diff options
| author | Olivier Dony <odo@openerp.com> | 2012-10-01 16:53:08 +0200 |
|---|---|---|
| committer | Olivier Dony <odo@openerp.com> | 2012-10-01 16:53:08 +0200 |
| commit | 9e6920415e41a052cffa7b898bf044dbf82b5ccb (patch) | |
| tree | 05e8d64788da1ff9f65e7406e3ff3d96d58a5762 /addons/web/static | |
| parent | 4ed79f3457a0252dcee4780385dc474ff2af356e (diff) | |
| parent | 1370ae133d4477d0a7a2239732f529a1eef39824 (diff) | |
[MERGE] Sync with trunk
bzr revid: odo@openerp.com-20121001145308-ri5rp2cazxqxcxme
Diffstat (limited to 'addons/web/static')
71 files changed, 12348 insertions, 20033 deletions
diff --git a/addons/web/static/lib/backbone/backbone.js b/addons/web/static/lib/backbone/backbone.js index 7c56f42363c..3373c952bfa 100644 --- a/addons/web/static/lib/backbone/backbone.js +++ b/addons/web/static/lib/backbone/backbone.js @@ -1,4 +1,4 @@ -// Backbone.js 0.9.1 +// Backbone.js 0.9.2 // (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc. // Backbone may be freely distributed under the MIT license. @@ -32,7 +32,7 @@ } // Current version of the library. Keep in sync with `package.json`. - Backbone.VERSION = '0.9.1'; + Backbone.VERSION = '0.9.2'; // Require Underscore, if we're on the server, and it's not already present. var _ = root._; @@ -71,6 +71,9 @@ // Backbone.Events // ----------------- + // Regular expression used to split event strings + var eventSplitter = /\s+/; + // A module that can be mixed in to *any object* in order to provide it with // custom events. You may bind with `on` or remove with `off` callback functions // to an event; trigger`-ing an event fires all callbacks in succession. @@ -80,89 +83,110 @@ // object.on('expand', function(){ alert('expanded'); }); // object.trigger('expand'); // - Backbone.Events = { + var Events = Backbone.Events = { - // Bind an event, specified by a string name, `ev`, to a `callback` + // Bind one or more space separated events, `events`, to a `callback` // function. Passing `"all"` will bind the callback to all events fired. on: function(events, callback, context) { - var ev; - events = events.split(/\s+/); - var calls = this._callbacks || (this._callbacks = {}); - while (ev = events.shift()) { - // Create an immutable callback list, allowing traversal during - // modification. The tail is an empty object that will always be used - // as the next node. - var list = calls[ev] || (calls[ev] = {}); - var tail = list.tail || (list.tail = list.next = {}); - tail.callback = callback; - tail.context = context; - list.tail = tail.next = {}; + + var calls, event, node, tail, list; + if (!callback) return this; + events = events.split(eventSplitter); + calls = this._callbacks || (this._callbacks = {}); + + // Create an immutable callback list, allowing traversal during + // modification. The tail is an empty object that will always be used + // as the next node. + while (event = events.shift()) { + list = calls[event]; + node = list ? list.tail : {}; + node.next = tail = {}; + node.context = context; + node.callback = callback; + calls[event] = {tail: tail, next: list ? list.next : node}; } + return this; }, // Remove one or many callbacks. If `context` is null, removes all callbacks // with that function. If `callback` is null, removes all callbacks for the - // event. If `ev` is null, removes all bound callbacks for all events. + // event. If `events` is null, removes all bound callbacks for all events. off: function(events, callback, context) { - var ev, calls, node; - if (!events) { + var event, calls, node, tail, cb, ctx; + + // No events, or removing *all* events. + if (!(calls = this._callbacks)) return; + if (!(events || callback || context)) { delete this._callbacks; - } else if (calls = this._callbacks) { - events = events.split(/\s+/); - while (ev = events.shift()) { - node = calls[ev]; - delete calls[ev]; - if (!callback || !node) continue; - // Create a new list, omitting the indicated event/context pairs. - while ((node = node.next) && node.next) { - if (node.callback === callback && - (!context || node.context === context)) continue; - this.on(ev, node.callback, node.context); + return this; + } + + // Loop through the listed events and contexts, splicing them out of the + // linked list of callbacks if appropriate. + events = events ? events.split(eventSplitter) : _.keys(calls); + while (event = events.shift()) { + node = calls[event]; + delete calls[event]; + if (!node || !(callback || context)) continue; + // Create a new list, omitting the indicated callbacks. + tail = node.tail; + while ((node = node.next) !== tail) { + cb = node.callback; + ctx = node.context; + if ((callback && cb !== callback) || (context && ctx !== context)) { + this.on(event, cb, ctx); } } } + return this; }, - // Trigger an event, firing all bound callbacks. Callbacks are passed the - // same arguments as `trigger` is, apart from the event name. - // Listening for `"all"` passes the true event name as the first argument. + // Trigger one or many events, firing all bound callbacks. Callbacks are + // passed the same arguments as `trigger` is, apart from the event name + // (unless you're listening on `"all"`, which will cause your callback to + // receive the true name of the event as the first argument). trigger: function(events) { var event, node, calls, tail, args, all, rest; if (!(calls = this._callbacks)) return this; - all = calls['all']; - (events = events.split(/\s+/)).push(null); - // Save references to the current heads & tails. - while (event = events.shift()) { - if (all) events.push({next: all.next, tail: all.tail, event: event}); - if (!(node = calls[event])) continue; - events.push({next: node.next, tail: node.tail}); - } - // Traverse each list, stopping when the saved tail is reached. + all = calls.all; + events = events.split(eventSplitter); rest = slice.call(arguments, 1); - while (node = events.pop()) { - tail = node.tail; - args = node.event ? [node.event].concat(rest) : rest; - while ((node = node.next) !== tail) { - node.callback.apply(node.context || this, args); + + // For each event, walk through the linked list of callbacks twice, + // first to trigger the event, then to trigger any `"all"` callbacks. + while (event = events.shift()) { + if (node = calls[event]) { + tail = node.tail; + while ((node = node.next) !== tail) { + node.callback.apply(node.context || this, rest); + } + } + if (node = all) { + tail = node.tail; + args = [event].concat(rest); + while ((node = node.next) !== tail) { + node.callback.apply(node.context || this, args); + } } } + return this; } }; // Aliases for backwards compatibility. - Backbone.Events.bind = Backbone.Events.on; - Backbone.Events.unbind = Backbone.Events.off; + Events.bind = Events.on; + Events.unbind = Events.off; // Backbone.Model // -------------- // Create a new model, with defined attributes. A client id (`cid`) // is automatically generated and assigned for you. - Backbone.Model = function(attributes, options) { + var Model = Backbone.Model = function(attributes, options) { var defaults; attributes || (attributes = {}); if (options && options.parse) attributes = this.parse(attributes); @@ -173,16 +197,31 @@ this.attributes = {}; this._escapedAttributes = {}; this.cid = _.uniqueId('c'); - if (!this.set(attributes, {silent: true})) { - throw new Error("Can't create an invalid model"); - } - delete this._changed; + this.changed = {}; + this._silent = {}; + this._pending = {}; + this.set(attributes, {silent: true}); + // Reset change tracking. + this.changed = {}; + this._silent = {}; + this._pending = {}; this._previousAttributes = _.clone(this.attributes); this.initialize.apply(this, arguments); }; // Attach all inheritable methods to the Model prototype. - _.extend(Backbone.Model.prototype, Backbone.Events, { + _.extend(Model.prototype, Events, { + + // A hash of attributes whose current and previous value differ. + changed: null, + + // A hash of attributes that have silently changed since the last time + // `change` was called. Will become pending attributes on the next call. + _silent: null, + + // A hash of attributes that have changed since the last `'change'` event + // began. + _pending: null, // The default name for the JSON `id` attribute is `"id"`. MongoDB and // CouchDB users may want to set this to `"_id"`. @@ -193,7 +232,7 @@ initialize: function(){}, // Return a copy of the model's `attributes` object. - toJSON: function() { + toJSON: function(options) { return _.clone(this.attributes); }, @@ -206,20 +245,22 @@ escape: function(attr) { var html; if (html = this._escapedAttributes[attr]) return html; - var val = this.attributes[attr]; + var val = this.get(attr); return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val); }, // Returns `true` if the attribute contains a value that is not null // or undefined. has: function(attr) { - return this.attributes[attr] != null; + return this.get(attr) != null; }, // Set a hash of model attributes on the object, firing `"change"` unless // you choose to silence it. set: function(key, value, options) { var attrs, attr, val; + + // Handle both `"key", value` and `{key: value}` -style arguments. if (_.isObject(key) || key == null) { attrs = key; options = value; @@ -231,7 +272,7 @@ // Extract attributes and options. options || (options = {}); if (!attrs) return this; - if (attrs instanceof Backbone.Model) attrs = attrs.attributes; + if (attrs instanceof Model) attrs = attrs.attributes; if (options.unset) for (attr in attrs) attrs[attr] = void 0; // Run validation. @@ -240,33 +281,37 @@ // Check for changes of `id`. if (this.idAttribute in attrs) this.id = attrs[this.idAttribute]; + var changes = options.changes = {}; var now = this.attributes; var escaped = this._escapedAttributes; var prev = this._previousAttributes || {}; - var alreadySetting = this._setting; - this._changed || (this._changed = {}); - this._setting = true; - // Update attributes. + // For each `set` attribute... for (attr in attrs) { val = attrs[attr]; - if (!_.isEqual(now[attr], val)) delete escaped[attr]; - options.unset ? delete now[attr] : now[attr] = val; - if (this._changing && !_.isEqual(this._changed[attr], val)) { - this.trigger('change:' + attr, this, val, options); - this._moreChanges = true; + + // If the new and current value differ, record the change. + if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) { + delete escaped[attr]; + (options.silent ? this._silent : changes)[attr] = true; } - delete this._changed[attr]; + + // Update or delete the current value. + options.unset ? delete now[attr] : now[attr] = val; + + // If the new and previous value differ, record the change. If not, + // then remove changes for this attribute. if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) { - this._changed[attr] = val; + this.changed[attr] = val; + if (!options.silent) this._pending[attr] = true; + } else { + delete this.changed[attr]; + delete this._pending[attr]; } } - // Fire the `"change"` events, if the model has been changed. - if (!alreadySetting) { - if (!options.silent && this.hasChanged()) this.change(options); - this._setting = false; - } + // Fire the `"change"` events. + if (!options.silent) this.change(options); return this; }, @@ -304,6 +349,8 @@ // state will be `set` again. save: function(key, value, options) { var attrs, current; + + // Handle both `("key", value)` and `({key: value})` -style calls. if (_.isObject(key) || key == null) { attrs = key; options = value; @@ -311,18 +358,30 @@ attrs = {}; attrs[key] = value; } - options = options ? _.clone(options) : {}; - if (options.wait) current = _.clone(this.attributes); + + // If we're "wait"-ing to set changed attributes, validate early. + if (options.wait) { + if (!this._validate(attrs, options)) return false; + current = _.clone(this.attributes); + } + + // Regular saves `set` attributes before persisting to the server. var silentOptions = _.extend({}, options, {silent: true}); if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) { return false; } + + // After a successful server-side save, the client is (optionally) + // updated with the server-side state. var model = this; var success = options.success; options.success = function(resp, status, xhr) { var serverAttrs = model.parse(resp, xhr); - if (options.wait) serverAttrs = _.extend(attrs || {}, serverAttrs); + if (options.wait) { + delete options.wait; + serverAttrs = _.extend(attrs || {}, serverAttrs); + } if (!model.set(serverAttrs, options)) return false; if (success) { success(model, resp); @@ -330,6 +389,8 @@ model.trigger('sync', model, resp, options); } }; + + // Finish configuring and sending the Ajax request. options.error = Backbone.wrapError(options.error, model, options); var method = this.isNew() ? 'create' : 'update'; var xhr = (this.sync || Backbone.sync).call(this, method, this, options); @@ -349,7 +410,11 @@ model.trigger('destroy', model, model.collection, options); }; - if (this.isNew()) return triggerDestroy(); + if (this.isNew()) { + triggerDestroy(); + return false; + } + options.success = function(resp) { if (options.wait) triggerDestroy(); if (success) { @@ -358,6 +423,7 @@ model.trigger('sync', model, resp, options); } }; + options.error = Backbone.wrapError(options.error, model, options); var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options); if (!options.wait) triggerDestroy(); @@ -368,7 +434,7 @@ // using Backbone's restful methods, override this to change the endpoint // that will be called. url: function() { - var base = getValue(this.collection, 'url') || getValue(this, 'urlRoot') || urlError(); + var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError(); if (this.isNew()) return base; return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id); }, @@ -393,18 +459,33 @@ // a `"change:attribute"` event for each changed attribute. // Calling this will cause all objects observing the model to update. change: function(options) { - if (this._changing || !this.hasChanged()) return this; + options || (options = {}); + var changing = this._changing; this._changing = true; - this._moreChanges = true; - for (var attr in this._changed) { - this.trigger('change:' + attr, this, this._changed[attr], options); + + // Silent changes become pending changes. + for (var attr in this._silent) this._pending[attr] = true; + + // Silent changes are triggered. + var changes = _.extend({}, options.changes, this._silent); + this._silent = {}; + for (var attr in changes) { + this.trigger('change:' + attr, this, this.get(attr), options); } - while (this._moreChanges) { - this._moreChanges = false; + if (changing) return this; + + // Continue firing `"change"` events while there are pending changes. + while (!_.isEmpty(this._pending)) { + this._pending = {}; this.trigger('change', this, options); + // Pending and silent changes still remain. + for (var attr in this.changed) { + if (this._pending[attr] || this._silent[attr]) continue; + delete this.changed[attr]; + } + this._previousAttributes = _.clone(this.attributes); } - this._previousAttributes = _.clone(this.attributes); - delete this._changed; + this._changing = false; return this; }, @@ -412,8 +493,8 @@ // Determine if the model has changed since the last `"change"` event. // If you specify an attribute name, determine if that attribute has changed. hasChanged: function(attr) { - if (!arguments.length) return !_.isEmpty(this._changed); - return this._changed && _.has(this._changed, attr); + if (!arguments.length) return !_.isEmpty(this.changed); + return _.has(this.changed, attr); }, // Return an object containing all the attributes that have changed, or @@ -423,7 +504,7 @@ // You can also pass an attributes object to diff against the model, // determining if there *would be* a change. changedAttributes: function(diff) { - if (!diff) return this.hasChanged() ? _.clone(this._changed) : false; + if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; var val, changed = false, old = this._previousAttributes; for (var attr in diff) { if (_.isEqual(old[attr], (val = diff[attr]))) continue; @@ -451,9 +532,9 @@ return !this.validate(this.attributes); }, - // Run validation against a set of incoming attributes, returning `true` - // if all is well. If a specific `error` callback has been passed, - // call that instead of firing the general `"error"` event. + // Run validation against the next complete set of model attributes, + // returning `true` if all is well. If a specific `error` callback has + // been passed, call that instead of firing the general `"error"` event. _validate: function(attrs, options) { if (options.silent || !this.validate) return true; attrs = _.extend({}, this.attributes, attrs); @@ -475,8 +556,9 @@ // Provides a standard collection class for our sets of models, ordered // or unordered. If a `comparator` is specified, the Collection will maintain // its models in sort order, as they're added and removed. - Backbone.Collection = function(models, options) { + var Collection = Backbone.Collection = function(models, options) { options || (options = {}); + if (options.model) this.model = options.model; if (options.comparator) this.comparator = options.comparator; this._reset(); this.initialize.apply(this, arguments); @@ -484,11 +566,11 @@ }; // Define the Collection's inheritable methods. - _.extend(Backbone.Collection.prototype, Backbone.Events, { + _.extend(Collection.prototype, Events, { // The default model for a collection is just a **Backbone.Model**. // This should be overridden in most cases. - model: Backbone.Model, + model: Model, // Initialize is an empty function by default. Override it with your own // initialization logic. @@ -496,14 +578,14 @@ // The JSON representation of a Collection is an array of the // models' attributes. - toJSON: function() { - return this.map(function(model){ return model.toJSON(); }); + toJSON: function(options) { + return this.map(function(model){ return model.toJSON(options); }); }, // Add a model, or list of models to the set. Pass **silent** to avoid // firing the `add` event for every new model. add: function(models, options) { - var i, index, length, model, cid, id, cids = {}, ids = {}; + var i, index, length, model, cid, id, cids = {}, ids = {}, dups = []; options || (options = {}); models = _.isArray(models) ? models.slice() : [models]; @@ -513,16 +595,24 @@ if (!(model = models[i] = this._prepareModel(models[i], options))) { throw new Error("Can't add an invalid model to a collection"); } - if (cids[cid = model.cid] || this._byCid[cid] || - (((id = model.id) != null) && (ids[id] || this._byId[id]))) { - throw new Error("Can't add the same model to a collection twice"); + cid = model.cid; + id = model.id; + if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) { + dups.push(i); + continue; } cids[cid] = ids[id] = model; } + // Remove duplicates. + i = dups.length; + while (i--) { + models.splice(dups[i], 1); + } + // Listen to added models' events, and index models for lookup by // `id` and by `cid`. - for (i = 0; i < length; i++) { + for (i = 0, length = models.length; i < length; i++) { (model = models[i]).on('all', this._onModelEvent, this); this._byCid[model.cid] = model; if (model.id != null) this._byId[model.id] = model; @@ -566,9 +656,37 @@ return this; }, + // Add a model to the end of the collection. + push: function(model, options) { + model = this._prepareModel(model, options); + this.add(model, options); + return model; + }, + + // Remove a model from the end of the collection. + pop: function(options) { + var model = this.at(this.length - 1); + this.remove(model, options); + return model; + }, + + // Add a model to the beginning of the collection. + unshift: function(model, options) { + model = this._prepareModel(model, options); + this.add(model, _.extend({at: 0}, options)); + return model; + }, + + // Remove a model from the beginning of the collection. + shift: function(options) { + var model = this.at(0); + this.remove(model, options); + return model; + }, + // Get a model from the set by id. get: function(id) { - if (id == null) return null; + if (id == null) return void 0; return this._byId[id.id != null ? id.id : id]; }, @@ -582,6 +700,17 @@ return this.models[index]; }, + // Return models with matching attributes. Useful for simple cases of `filter`. + where: function(attrs) { + if (_.isEmpty(attrs)) return []; + return this.filter(function(model) { + for (var key in attrs) { + if (attrs[key] !== model.get(key)) return false; + } + return true; + }); + }, + // Force the collection to re-sort itself. You don't need to call this under // normal circumstances, as the set will maintain sort order as each item // is added. @@ -613,7 +742,7 @@ this._removeReference(this.models[i]); } this._reset(); - this.add(models, {silent: true, parse: options.parse}); + this.add(models, _.extend({silent: true}, options)); if (!options.silent) this.trigger('reset', this, options); return this; }, @@ -679,7 +808,8 @@ // Prepare a model or hash of attributes to be added to this collection. _prepareModel: function(model, options) { - if (!(model instanceof Backbone.Model)) { + options || (options = {}); + if (!(model instanceof Model)) { var attrs = model; options.collection = this; model = new this.model(attrs, options); @@ -702,12 +832,12 @@ // Sets need to update their indexes when models change ids. All other // events simply proxy through. "add" and "remove" events that originate // in other collections are ignored. - _onModelEvent: function(ev, model, collection, options) { - if ((ev == 'add' || ev == 'remove') && collection != this) return; - if (ev == 'destroy') { + _onModelEvent: function(event, model, collection, options) { + if ((event == 'add' || event == 'remove') && collection != this) return; + if (event == 'destroy') { this.remove(model, options); } - if (model && ev === 'change:' + model.idAttribute) { + if (model && event === 'change:' + model.idAttribute) { delete this._byId[model.previous(model.idAttribute)]; this._byId[model.id] = model; } @@ -725,7 +855,7 @@ // Mix in each Underscore method as a proxy to `Collection#models`. _.each(methods, function(method) { - Backbone.Collection.prototype[method] = function() { + Collection.prototype[method] = function() { return _[method].apply(_, [this.models].concat(_.toArray(arguments))); }; }); @@ -735,7 +865,7 @@ // Routers map faux-URLs to actions, and fire events when routes are // matched. Creating a new one sets its `routes` hash, if not set statically. - Backbone.Router = function(options) { + var Router = Backbone.Router = function(options) { options || (options = {}); if (options.routes) this.routes = options.routes; this._bindRoutes(); @@ -749,7 +879,7 @@ var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g; // Set up all inheritable **Backbone.Router** properties and methods. - _.extend(Backbone.Router.prototype, Backbone.Events, { + _.extend(Router.prototype, Events, { // Initialize is an empty function by default. Override it with your own // initialization logic. @@ -762,7 +892,7 @@ // }); // route: function(route, name, callback) { - Backbone.history || (Backbone.history = new Backbone.History); + Backbone.history || (Backbone.history = new History); if (!_.isRegExp(route)) route = this._routeToRegExp(route); if (!callback) callback = this[name]; Backbone.history.route(route, _.bind(function(fragment) { @@ -815,7 +945,7 @@ // Handles cross-browser history management, based on URL fragments. If the // browser does not support `onhashchange`, falls back to polling. - Backbone.History = function() { + var History = Backbone.History = function() { this.handlers = []; _.bindAll(this, 'checkUrl'); }; @@ -827,15 +957,23 @@ var isExplorer = /msie [\w.]+/; // Has the history handling already been started? - var historyStarted = false; + History.started = false; // Set up all inheritable **Backbone.History** properties and methods. - _.extend(Backbone.History.prototype, Backbone.Events, { + _.extend(History.prototype, Events, { // The default interval to poll for hash changes, if necessary, is // twenty times a second. interval: 50, + // Gets the true hash value. Cannot use location.hash directly due to bug + // in Firefox where location.hash will always be decoded. + getHash: function(windowOverride) { + var loc = windowOverride ? windowOverride.location : window.location; + var match = loc.href.match(/#(.*)$/); + return match ? match[1] : ''; + }, + // Get the cross-browser normalized URL fragment, either from the URL, // the hash, or the override. getFragment: function(fragment, forcePushState) { @@ -845,10 +983,9 @@ var search = window.location.search; if (search) fragment += search; } else { - fragment = window.location.hash; + fragment = this.getHash(); } } - fragment = decodeURIComponent(fragment); if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length); return fragment.replace(routeStripper, ''); }, @@ -856,10 +993,11 @@ // Start the hash change handling, returning `true` if the current URL matches // an existing route, and `false` otherwise. start: function(options) { + if (History.started) throw new Error("Backbone.history has already been started"); + History.started = true; // Figure out the initial configuration. Do we need an iframe? // Is pushState desired ... is it available? - if (historyStarted) throw new Error("Backbone.history has already been started"); this.options = _.extend({}, {root: '/'}, this.options, options); this._wantsHashChange = this.options.hashChange !== false; this._wantsPushState = !!this.options.pushState; @@ -867,6 +1005,7 @@ var fragment = this.getFragment(); var docMode = document.documentMode; var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7)); + if (oldIE) { this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow; this.navigate(fragment); @@ -885,7 +1024,6 @@ // Determine if we need to change the base url, for a pushState link // opened by a non-pushState browser. this.fragment = fragment; - historyStarted = true; var loc = window.location; var atRoot = loc.pathname == this.options.root; @@ -900,7 +1038,7 @@ // Or if we've started out with a hash-based route, but we're currently // in a browser where it could be `pushState`-based instead... } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) { - this.fragment = loc.hash.replace(routeStripper, ''); + this.fragment = this.getHash().replace(routeStripper, ''); window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment); } @@ -914,7 +1052,7 @@ stop: function() { $(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl); clearInterval(this._checkUrlInterval); - historyStarted = false; + History.started = false; }, // Add a route to be tested when the fragment changes. Routes added later @@ -927,10 +1065,10 @@ // calls `loadUrl`, normalizing across the hidden iframe. checkUrl: function(e) { var current = this.getFragment(); - if (current == this.fragment && this.iframe) current = this.getFragment(this.iframe.location.hash); - if (current == this.fragment || current == decodeURIComponent(this.fragment)) return false; + if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe)); + if (current == this.fragment) return false; if (this.iframe) this.navigate(current); - this.loadUrl() || this.loadUrl(window.location.hash); + this.loadUrl() || this.loadUrl(this.getHash()); }, // Attempt to load the current URL fragment. If a route succeeds with a @@ -953,12 +1091,12 @@ // // The options object can contain `trigger: true` if you wish to have the // route callback be fired (not usually desirable), or `replace: true`, if - // you which to modify the current URL without adding an entry to the history. + // you wish to modify the current URL without adding an entry to the history. navigate: function(fragment, options) { - if (!historyStarted) return false; + if (!History.started) return false; if (!options || options === true) options = {trigger: options}; var frag = (fragment || '').replace(routeStripper, ''); - if (this.fragment == frag || this.fragment == decodeURIComponent(frag)) return; + if (this.fragment == frag) return; // If pushState is available, we use it to set the fragment as a real URL. if (this._hasPushState) { @@ -971,7 +1109,7 @@ } else if (this._wantsHashChange) { this.fragment = frag; this._updateHash(window.location, frag, options.replace); - if (this.iframe && (frag != this.getFragment(this.iframe.location.hash))) { + if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) { // Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change. // When replace is true, we don't want this. if(!options.replace) this.iframe.document.open().close(); @@ -1002,7 +1140,7 @@ // Creating a Backbone.View creates its initial element outside of the DOM, // if an existing element is not provided... - Backbone.View = function(options) { + var View = Backbone.View = function(options) { this.cid = _.uniqueId('view'); this._configure(options || {}); this._ensureElement(); @@ -1011,13 +1149,13 @@ }; // Cached regex to split keys for `delegate`. - var eventSplitter = /^(\S+)\s*(.*)$/; + var delegateEventSplitter = /^(\S+)\s*(.*)$/; // List of view options to be merged as properties. var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName']; // Set up all inheritable **Backbone.View** properties and methods. - _.extend(Backbone.View.prototype, Backbone.Events, { + _.extend(View.prototype, Events, { // The default `tagName` of a View's element is `"div"`. tagName: 'div', @@ -1061,7 +1199,8 @@ // Change the view's element (`this.el` property), including event // re-delegation. setElement: function(element, delegate) { - this.$el = $(element); + if (this.$el) this.undelegateEvents(); + this.$el = (element instanceof $) ? element : $(element); this.el = this.$el[0]; if (delegate !== false) this.delegateEvents(); return this; @@ -1088,8 +1227,8 @@ for (var key in events) { var method = events[key]; if (!_.isFunction(method)) method = this[events[key]]; - if (!method) throw new Error('Event "' + events[key] + '" does not exist'); - var match = key.match(eventSplitter); + if (!method) throw new Error('Method "' + events[key] + '" does not exist'); + var match = key.match(delegateEventSplitter); var eventName = match[1], selector = match[2]; method = _.bind(method, this); eventName += '.delegateEvents' + this.cid; @@ -1145,8 +1284,7 @@ }; // Set up inheritance for the model, collection, and view. - Backbone.Model.extend = Backbone.Collection.extend = - Backbone.Router.extend = Backbone.View.extend = extend; + Model.extend = Collection.extend = Router.extend = View.extend = extend; // Backbone.sync // ------------- @@ -1177,6 +1315,9 @@ Backbone.sync = function(method, model, options) { var type = methodMap[method]; + // Default options, unless specified. + options || (options = {}); + // Default JSON-request options. var params = {type: type, dataType: 'json'}; diff --git a/addons/web/static/lib/cleditor/images/buttons.gif b/addons/web/static/lib/cleditor/images/buttons.gif Binary files differnew file mode 100644 index 00000000000..2e464d0c8c7 --- /dev/null +++ b/addons/web/static/lib/cleditor/images/buttons.gif diff --git a/addons/web/static/lib/cleditor/images/toolbar.gif b/addons/web/static/lib/cleditor/images/toolbar.gif Binary files differnew file mode 100644 index 00000000000..e6eb2da5549 --- /dev/null +++ b/addons/web/static/lib/cleditor/images/toolbar.gif diff --git a/addons/web/static/lib/cleditor/jquery.cleditor.css b/addons/web/static/lib/cleditor/jquery.cleditor.css new file mode 100644 index 00000000000..6ac490bcfc4 --- /dev/null +++ b/addons/web/static/lib/cleditor/jquery.cleditor.css @@ -0,0 +1,24 @@ +.cleditorMain {border:1px solid #999; padding:0 1px 1px; background-color:white}
+.cleditorMain iframe {border:none; margin:0; padding:0}
+.cleditorMain textarea {border:none; margin:0; padding:0; overflow-y:scroll; font:10pt Arial,Verdana; resize:none; outline:none /* webkit grip focus */}
+.cleditorToolbar {background: url('images/toolbar.gif') repeat}
+.cleditorGroup {float:left; height:26px}
+.cleditorButton {float:left; width:24px; height:24px; margin:1px 0 1px 0; background: url('images/buttons.gif')}
+.cleditorDisabled {opacity:0.3; filter:alpha(opacity=30)}
+.cleditorDivider {float:left; width:1px; height:23px; margin:1px 0 1px 0; background:#CCC}
+.cleditorPopup {border:solid 1px #999; background-color:white; position:absolute; font:10pt Arial,Verdana; cursor:default; z-index:10000}
+.cleditorList div {padding:2px 4px 2px 4px}
+.cleditorList p,
+.cleditorList h1,
+.cleditorList h2,
+.cleditorList h3,
+.cleditorList h4,
+.cleditorList h5,
+.cleditorList h6,
+.cleditorList font {padding:0; margin:0; background-color:Transparent}
+.cleditorColor {width:150px; padding:1px 0 0 1px}
+.cleditorColor div {float:left; width:14px; height:14px; margin:0 1px 1px 0}
+.cleditorPrompt {background-color:#F6F7F9; padding:4px; font-size:8.5pt}
+.cleditorPrompt input,
+.cleditorPrompt textarea {font:8.5pt Arial,Verdana;}
+.cleditorMsg {background-color:#FDFCEE; width:150px; padding:4px; font-size:8.5pt}
diff --git a/addons/web/static/lib/cleditor/jquery.cleditor.js b/addons/web/static/lib/cleditor/jquery.cleditor.js new file mode 100644 index 00000000000..c0763136c99 --- /dev/null +++ b/addons/web/static/lib/cleditor/jquery.cleditor.js @@ -0,0 +1,1132 @@ +/**
+ @preserve CLEditor WYSIWYG HTML Editor v1.3.0
+ http://premiumsoftware.net/cleditor
+ requires jQuery v1.4.2 or later
+
+ Copyright 2010, Chris Landowski, Premium Software, LLC
+ Dual licensed under the MIT or GPL Version 2 licenses.
+*/
+
+// ==ClosureCompiler==
+// @compilation_level SIMPLE_OPTIMIZATIONS
+// @output_file_name jquery.cleditor.min.js
+// ==/ClosureCompiler==
+
+(function($) {
+
+ //==============
+ // jQuery Plugin
+ //==============
+
+ $.cleditor = {
+
+ // Define the defaults used for all new cleditor instances
+ defaultOptions: {
+ width: 500, // width not including margins, borders or padding
+ height: 250, // height not including margins, borders or padding
+ controls: // controls to add to the toolbar
+ "bold italic underline strikethrough subscript superscript | font size " +
+ "style | color highlight removeformat | bullets numbering | outdent " +
+ "indent | alignleft center alignright justify | undo redo | " +
+ "rule image link unlink | cut copy paste pastetext | print source",
+ colors: // colors in the color popup
+ "FFF FCC FC9 FF9 FFC 9F9 9FF CFF CCF FCF " +
+ "CCC F66 F96 FF6 FF3 6F9 3FF 6FF 99F F9F " +
+ "BBB F00 F90 FC6 FF0 3F3 6CC 3CF 66C C6C " +
+ "999 C00 F60 FC3 FC0 3C0 0CC 36F 63F C3C " +
+ "666 900 C60 C93 990 090 399 33F 60C 939 " +
+ "333 600 930 963 660 060 366 009 339 636 " +
+ "000 300 630 633 330 030 033 006 309 303",
+ fonts: // font names in the font popup
+ "Arial,Arial Black,Comic Sans MS,Courier New,Narrow,Garamond," +
+ "Georgia,Impact,Sans Serif,Serif,Tahoma,Trebuchet MS,Verdana",
+ sizes: // sizes in the font size popup
+ "1,2,3,4,5,6,7",
+ styles: // styles in the style popup
+ [["Paragraph", "<p>"], ["Header 1", "<h1>"], ["Header 2", "<h2>"],
+ ["Header 3", "<h3>"], ["Header 4","<h4>"], ["Header 5","<h5>"],
+ ["Header 6","<h6>"]],
+ useCSS: false, // use CSS to style HTML when possible (not supported in ie)
+ docType: // Document type contained within the editor
+ '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',
+ docCSSFile: // CSS file used to style the document contained within the editor
+ "",
+ bodyStyle: // style to assign to document body contained within the editor
+ "margin:4px; font:10pt Arial,Verdana; cursor:text"
+ },
+
+ // Define all usable toolbar buttons - the init string property is
+ // expanded during initialization back into the buttons object and
+ // seperate object properties are created for each button.
+ // e.g. buttons.size.title = "Font Size"
+ buttons: {
+ // name,title,command,popupName (""=use name)
+ init:
+ "bold,,|" +
+ "italic,,|" +
+ "underline,,|" +
+ "strikethrough,,|" +
+ "subscript,,|" +
+ "superscript,,|" +
+ "font,,fontname,|" +
+ "size,Font Size,fontsize,|" +
+ "style,,formatblock,|" +
+ "color,Font Color,forecolor,|" +
+ "highlight,Text Highlight Color,hilitecolor,color|" +
+ "removeformat,Remove Formatting,|" +
+ "bullets,,insertunorderedlist|" +
+ "numbering,,insertorderedlist|" +
+ "outdent,,|" +
+ "indent,,|" +
+ "alignleft,Align Text Left,justifyleft|" +
+ "center,,justifycenter|" +
+ "alignright,Align Text Right,justifyright|" +
+ "justify,,justifyfull|" +
+ "undo,,|" +
+ "redo,,|" +
+ "rule,Insert Horizontal Rule,inserthorizontalrule|" +
+ "image,Insert Image,insertimage,url|" +
+ "link,Insert Hyperlink,createlink,url|" +
+ "unlink,Remove Hyperlink,|" +
+ "cut,,|" +
+ "copy,,|" +
+ "paste,,|" +
+ "pastetext,Paste as Text,inserthtml,|" +
+ "print,,|" +
+ "source,Show Source"
+ },
+
+ // imagesPath - returns the path to the images folder
+ imagesPath: function() { return imagesPath(); }
+
+ };
+
+ // cleditor - creates a new editor for each of the matched textareas
+ $.fn.cleditor = function(options) {
+
+ // Create a new jQuery object to hold the results
+ var $result = $([]);
+
+ // Loop through all matching textareas and create the editors
+ this.each(function(idx, elem) {
+ if (elem.tagName == "TEXTAREA") {
+ var data = $.data(elem, CLEDITOR);
+ if (!data) data = new cleditor(elem, options);
+ $result = $result.add(data);
+ }
+ });
+
+ // return the new jQuery object
+ return $result;
+
+ };
+
+ //==================
+ // Private Variables
+ //==================
+
+ var
+
+ // Misc constants
+ BACKGROUND_COLOR = "backgroundColor",
+ BUTTON = "button",
+ BUTTON_NAME = "buttonName",
+ CHANGE = "change",
+ CLEDITOR = "cleditor",
+ CLICK = "click",
+ DISABLED = "disabled",
+ DIV_TAG = "<div>",
+ TRANSPARENT = "transparent",
+ UNSELECTABLE = "unselectable",
+
+ // Class name constants
+ MAIN_CLASS = "cleditorMain", // main containing div
+ TOOLBAR_CLASS = "cleditorToolbar", // toolbar div inside main div
+ GROUP_CLASS = "cleditorGroup", // group divs inside the toolbar div
+ BUTTON_CLASS = "cleditorButton", // button divs inside group div
+ DISABLED_CLASS = "cleditorDisabled",// disabled button divs
+ DIVIDER_CLASS = "cleditorDivider", // divider divs inside group div
+ POPUP_CLASS = "cleditorPopup", // popup divs inside body
+ LIST_CLASS = "cleditorList", // list popup divs inside body
+ COLOR_CLASS = "cleditorColor", // color popup div inside body
+ PROMPT_CLASS = "cleditorPrompt", // prompt popup divs inside body
+ MSG_CLASS = "cleditorMsg", // message popup div inside body
+
+ // Test for ie
+ ie = $.browser.msie,
+ ie6 = /msie\s6/i.test(navigator.userAgent),
+
+ // Test for iPhone/iTouch/iPad
+ iOS = /iphone|ipad|ipod/i.test(navigator.userAgent),
+
+ // Popups are created once as needed and shared by all editor instances
+ popups = {},
+
+ // Used to prevent the document click event from being bound more than once
+ documentClickAssigned,
+
+ // Local copy of the buttons object
+ buttons = $.cleditor.buttons;
+
+ //===============
+ // Initialization
+ //===============
+
+ // Expand the buttons.init string back into the buttons object
+ // and create seperate object properties for each button.
+ // e.g. buttons.size.title = "Font Size"
+ $.each(buttons.init.split("|"), function(idx, button) {
+ var items = button.split(","), name = items[0];
+ buttons[name] = {
+ stripIndex: idx,
+ name: name,
+ title: items[1] === "" ? name.charAt(0).toUpperCase() + name.substr(1) : items[1],
+ command: items[2] === "" ? name : items[2],
+ popupName: items[3] === "" ? name : items[3]
+ };
+ });
+ delete buttons.init;
+
+ //============
+ // Constructor
+ //============
+
+ // cleditor - creates a new editor for the passed in textarea element
+ cleditor = function(area, options) {
+
+ var editor = this;
+
+ // Get the defaults and override with options
+ editor.options = options = $.extend({}, $.cleditor.defaultOptions, options);
+
+ // Hide the textarea and associate it with this editor
+ var $area = editor.$area = $(area)
+ .hide()
+ .data(CLEDITOR, editor)
+ .blur(function() {
+ // Update the iframe when the textarea loses focus
+ updateFrame(editor, true);
+ });
+
+ // Create the main container and append the textarea
+ var $main = editor.$main = $(DIV_TAG)
+ .addClass(MAIN_CLASS)
+ .width(options.width)
+ .height(options.height);
+
+ // Create the toolbar
+ var $toolbar = editor.$toolbar = $(DIV_TAG)
+ .addClass(TOOLBAR_CLASS)
+ .appendTo($main);
+
+ // Add the first group to the toolbar
+ var $group = $(DIV_TAG)
+ .addClass(GROUP_CLASS)
+ .appendTo($toolbar);
+
+ // Add the buttons to the toolbar
+ $.each(options.controls.split(" "), function(idx, buttonName) {
+ if (buttonName === "") return true;
+
+ // Divider
+ if (buttonName == "|") {
+
+ // Add a new divider to the group
+ var $div = $(DIV_TAG)
+ .addClass(DIVIDER_CLASS)
+ .appendTo($group);
+
+ // Create a new group
+ $group = $(DIV_TAG)
+ .addClass(GROUP_CLASS)
+ .appendTo($toolbar);
+
+ }
+
+ // Button
+ else {
+
+ // Get the button definition
+ var button = buttons[buttonName];
+
+ // Add a new button to the group
+ var $buttonDiv = $(DIV_TAG)
+ .data(BUTTON_NAME, button.name)
+ .addClass(BUTTON_CLASS)
+ .attr("title", button.title)
+ .bind(CLICK, $.proxy(buttonClick, editor))
+ .appendTo($group)
+ .hover(hoverEnter, hoverLeave);
+
+ // Prepare the button image
+ var map = {};
+ if (button.css) map = button.css;
+ else if (button.image) map.backgroundImage = imageUrl(button.image);
+ if (button.stripIndex) map.backgroundPosition = button.stripIndex * -24;
+ $buttonDiv.css(map);
+
+ // Add the unselectable attribute for ie
+ if (ie)
+ $buttonDiv.attr(UNSELECTABLE, "on");
+
+ // Create the popup
+ if (button.popupName)
+ createPopup(button.popupName, options, button.popupClass,
+ button.popupContent, button.popupHover);
+
+ }
+
+ });
+
+ // Add the main div to the DOM and append the textarea
+ $main.insertBefore($area)
+ .append($area);
+
+ // Bind the document click event handler
+ if (!documentClickAssigned) {
+ $(document).click(function(e) {
+ // Dismiss all non-prompt popups
+ var $target = $(e.target);
+ if (!$target.add($target.parents()).is("." + PROMPT_CLASS))
+ hidePopups();
+ });
+ documentClickAssigned = true;
+ }
+
+ // Bind the window resize event when the width or height is auto or %
+ if (/auto|%/.test("" + options.width + options.height))
+ $(window).resize(function() {refresh(editor);});
+
+ // Create the iframe and resize the controls
+ refresh(editor);
+
+ };
+
+ //===============
+ // Public Methods
+ //===============
+
+ var fn = cleditor.prototype,
+
+ // Expose the following private functions as methods on the cleditor object.
+ // The closure compiler will rename the private functions. However, the
+ // exposed method names on the cleditor object will remain fixed.
+ methods = [
+ ["clear", clear],
+ ["disable", disable],
+ ["execCommand", execCommand],
+ ["focus", focus],
+ ["hidePopups", hidePopups],
+ ["sourceMode", sourceMode, true],
+ ["refresh", refresh],
+ ["select", select],
+ ["selectedHTML", selectedHTML, true],
+ ["selectedText", selectedText, true],
+ ["showMessage", showMessage],
+ ["updateFrame", updateFrame],
+ ["updateTextArea", updateTextArea]
+ ];
+
+ $.each(methods, function(idx, method) {
+ fn[method[0]] = function() {
+ var editor = this, args = [editor];
+ // using each here would cast booleans into objects!
+ for(var x = 0; x < arguments.length; x++) {args.push(arguments[x]);}
+ var result = method[1].apply(editor, args);
+ if (method[2]) return result;
+ return editor;
+ };
+ });
+
+ // change - shortcut for .bind("change", handler) or .trigger("change")
+ fn.change = function(handler) {
+ var $this = $(this);
+ return handler ? $this.bind(CHANGE, handler) : $this.trigger(CHANGE);
+ };
+
+ //===============
+ // Event Handlers
+ //===============
+
+ // buttonClick - click event handler for toolbar buttons
+ function buttonClick(e) {
+
+ var editor = this,
+ buttonDiv = e.target,
+ buttonName = $.data(buttonDiv, BUTTON_NAME),
+ button = buttons[buttonName],
+ popupName = button.popupName,
+ popup = popups[popupName];
+
+ // Check if disabled
+ if (editor.disabled || $(buttonDiv).attr(DISABLED) == DISABLED)
+ return;
+
+ // Fire the buttonClick event
+ var data = {
+ editor: editor,
+ button: buttonDiv,
+ buttonName: buttonName,
+ popup: popup,
+ popupName: popupName,
+ command: button.command,
+ useCSS: editor.options.useCSS
+ };
+
+ if (button.buttonClick && button.buttonClick(e, data) === false)
+ return false;
+
+ // Toggle source
+ if (buttonName == "source") {
+
+ // Show the iframe
+ if (sourceMode(editor)) {
+ delete editor.range;
+ editor.$area.hide();
+ editor.$frame.show();
+ buttonDiv.title = button.title;
+ }
+
+ // Show the textarea
+ else {
+ editor.$frame.hide();
+ editor.$area.show();
+ buttonDiv.title = "Show Rich Text";
+ }
+
+ // Enable or disable the toolbar buttons
+ // IE requires the timeout
+ setTimeout(function() {refreshButtons(editor);}, 100);
+
+ }
+
+ // Check for rich text mode
+ else if (!sourceMode(editor)) {
+
+ // Handle popups
+ if (popupName) {
+ var $popup = $(popup);
+
+ // URL
+ if (popupName == "url") {
+
+ // Check for selection before showing the link url popup
+ if (buttonName == "link" && selectedText(editor) === "") {
+ showMessage(editor, "A selection is required when inserting a link.", buttonDiv);
+ return false;
+ }
+
+ // Wire up the submit button click event handler
+ $popup.children(":button")
+ .unbind(CLICK)
+ .bind(CLICK, function() {
+
+ // Insert the image or link if a url was entered
+ var $text = $popup.find(":text"),
+ url = $.trim($text.val());
+ if (url !== "")
+ execCommand(editor, data.command, url, null, data.button);
+
+ // Reset the text, hide the popup and set focus
+ $text.val("http://");
+ hidePopups();
+ focus(editor);
+
+ });
+
+ }
+
+ // Paste as Text
+ else if (popupName == "pastetext") {
+
+ // Wire up the submit button click event handler
+ $popup.children(":button")
+ .unbind(CLICK)
+ .bind(CLICK, function() {
+
+ // Insert the unformatted text replacing new lines with break tags
+ var $textarea = $popup.find("textarea"),
+ text = $textarea.val().replace(/\n/g, "<br />");
+ if (text !== "")
+ execCommand(editor, data.command, text, null, data.button);
+
+ // Reset the text, hide the popup and set focus
+ $textarea.val("");
+ hidePopups();
+ focus(editor);
+
+ });
+
+ }
+
+ // Show the popup if not already showing for this button
+ if (buttonDiv !== $.data(popup, BUTTON)) {
+ showPopup(editor, popup, buttonDiv);
+ return false; // stop propagination to document click
+ }
+
+ // propaginate to documnt click
+ return;
+
+ }
+
+ // Print
+ else if (buttonName == "print")
+ editor.$frame[0].contentWindow.print();
+
+ // All other buttons
+ else if (!execCommand(editor, data.command, data.value, data.useCSS, buttonDiv))
+ return false;
+
+ }
+
+ // Focus the editor
+ focus(editor);
+
+ }
+
+ // hoverEnter - mouseenter event handler for buttons and popup items
+ function hoverEnter(e) {
+ var $div = $(e.target).closest("div");
+ $div.css(BACKGROUND_COLOR, $div.data(BUTTON_NAME) ? "#FFF" : "#FFC");
+ }
+
+ // hoverLeave - mouseleave event handler for buttons and popup items
+ function hoverLeave(e) {
+ $(e.target).closest("div").css(BACKGROUND_COLOR, "transparent");
+ }
+
+ // popupClick - click event handler for popup items
+ function popupClick(e) {
+
+ var editor = this,
+ popup = e.data.popup,
+ target = e.target;
+
+ // Check for message and prompt popups
+ if (popup === popups.msg || $(popup).hasClass(PROMPT_CLASS))
+ return;
+
+ // Get the button info
+ var buttonDiv = $.data(popup, BUTTON),
+ buttonName = $.data(buttonDiv, BUTTON_NAME),
+ button = buttons[buttonName],
+ command = button.command,
+ value,
+ useCSS = editor.options.useCSS;
+
+ // Get the command value
+ if (buttonName == "font")
+ // Opera returns the fontfamily wrapped in quotes
+ value = target.style.fontFamily.replace(/"/g, "");
+ else if (buttonName == "size") {
+ if (target.tagName == "DIV")
+ target = target.children[0];
+ value = target.innerHTML;
+ }
+ else if (buttonName == "style")
+ value = "<" + target.tagName + ">";
+ else if (buttonName == "color")
+ value = hex(target.style.backgroundColor);
+ else if (buttonName == "highlight") {
+ value = hex(target.style.backgroundColor);
+ if (ie) command = 'backcolor';
+ else useCSS = true;
+ }
+
+ // Fire the popupClick event
+ var data = {
+ editor: editor,
+ button: buttonDiv,
+ buttonName: buttonName,
+ popup: popup,
+ popupName: button.popupName,
+ command: command,
+ value: value,
+ useCSS: useCSS
+ };
+
+ if (button.popupClick && button.popupClick(e, data) === false)
+ return;
+
+ // Execute the command
+ if (data.command && !execCommand(editor, data.command, data.value, data.useCSS, buttonDiv))
+ return false;
+
+ // Hide the popup and focus the editor
+ hidePopups();
+ focus(editor);
+
+ }
+
+ //==================
+ // Private Functions
+ //==================
+ + // checksum - returns a checksum using the Adler-32 method
+ function checksum(text)
+ {
+ var a = 1, b = 0;
+ for (var index = 0; index < text.length; ++index) {
+ a = (a + text.charCodeAt(index)) % 65521;
+ b = (b + a) % 65521;
+ }
+ return (b << 16) | a;
+ }
+
+ // clear - clears the contents of the editor
+ function clear(editor) {
+ editor.$area.val("");
+ updateFrame(editor);
+ }
+
+ // createPopup - creates a popup and adds it to the body
+ function createPopup(popupName, options, popupTypeClass, popupContent, popupHover) {
+
+ // Check if popup already exists
+ if (popups[popupName])
+ return popups[popupName];
+
+ // Create the popup
+ var $popup = $(DIV_TAG)
+ .hide()
+ .addClass(POPUP_CLASS)
+ .appendTo("body");
+
+ // Add the content
+
+ // Custom popup
+ if (popupContent)
+ $popup.html(popupContent);
+
+ // Color
+ else if (popupName == "color") {
+ var colors = options.colors.split(" ");
+ if (colors.length < 10)
+ $popup.width("auto");
+ $.each(colors, function(idx, color) {
+ $(DIV_TAG).appendTo($popup)
+ .css(BACKGROUND_COLOR, "#" + color);
+ });
+ popupTypeClass = COLOR_CLASS;
+ }
+
+ // Font
+ else if (popupName == "font")
+ $.each(options.fonts.split(","), function(idx, font) {
+ $(DIV_TAG).appendTo($popup)
+ .css("fontFamily", font)
+ .html(font);
+ });
+
+ // Size
+ else if (popupName == "size")
+ $.each(options.sizes.split(","), function(idx, size) {
+ $(DIV_TAG).appendTo($popup)
+ .html("<font size=" + size + ">" + size + "</font>");
+ });
+
+ // Style
+ else if (popupName == "style")
+ $.each(options.styles, function(idx, style) {
+ $(DIV_TAG).appendTo($popup)
+ .html(style[1] + style[0] + style[1].replace("<", "</"));
+ });
+
+ // URL
+ else if (popupName == "url") {
+ $popup.html('Enter URL:<br><input type=text value="http://" size=35><br><input type=button value="Submit">');
+ popupTypeClass = PROMPT_CLASS;
+ }
+
+ // Paste as Text
+ else if (popupName == "pastetext") {
+ $popup.html('Paste your content here and click submit.<br /><textarea cols=40 rows=3></textarea><br /><input type=button value=Submit>');
+ popupTypeClass = PROMPT_CLASS;
+ }
+
+ // Add the popup type class name
+ if (!popupTypeClass && !popupContent)
+ popupTypeClass = LIST_CLASS;
+ $popup.addClass(popupTypeClass);
+
+ // Add the unselectable attribute to all items
+ if (ie) {
+ $popup.attr(UNSELECTABLE, "on")
+ .find("div,font,p,h1,h2,h3,h4,h5,h6")
+ .attr(UNSELECTABLE, "on");
+ }
+
+ // Add the hover effect to all items
+ if ($popup.hasClass(LIST_CLASS) || popupHover === true)
+ $popup.children().hover(hoverEnter, hoverLeave);
+
+ // Add the popup to the array and return it
+ popups[popupName] = $popup[0];
+ return $popup[0];
+
+ }
+
+ // disable - enables or disables the editor
+ function disable(editor, disabled) {
+
+ // Update the textarea and save the state
+ if (disabled) {
+ editor.$area.attr(DISABLED, DISABLED);
+ editor.disabled = true;
+ }
+ else {
+ editor.$area.removeAttr(DISABLED);
+ delete editor.disabled;
+ }
+
+ // Switch the iframe into design mode.
+ // ie6 does not support designMode.
+ // ie7 & ie8 do not properly support designMode="off".
+ try {
+ if (ie) editor.doc.body.contentEditable = !disabled;
+ else editor.doc.designMode = !disabled ? "on" : "off";
+ }
+ // Firefox 1.5 throws an exception that can be ignored
+ // when toggling designMode from off to on.
+ catch (err) {}
+
+ // Enable or disable the toolbar buttons
+ refreshButtons(editor);
+
+ }
+
+ // execCommand - executes a designMode command
+ function execCommand(editor, command, value, useCSS, button) {
+
+ // Restore the current ie selection
+ restoreRange(editor);
+
+ // Set the styling method
+ if (!ie) {
+ if (useCSS === undefined || useCSS === null)
+ useCSS = editor.options.useCSS;
+ editor.doc.execCommand("styleWithCSS", 0, useCSS.toString());
+ }
+
+ // Execute the command and check for error
+ var success = true, description;
+ if (ie && command.toLowerCase() == "inserthtml")
+ getRange(editor).pasteHTML(value);
+ else {
+ try { success = editor.doc.execCommand(command, 0, value || null); }
+ catch (err) { description = err.description; success = false; }
+ if (!success) {
+ if ("cutcopypaste".indexOf(command) > -1)
+ showMessage(editor, "For security reasons, your browser does not support the " +
+ command + " command. Try using the keyboard shortcut or context menu instead.",
+ button);
+ else
+ showMessage(editor,
+ (description ? description : "Error executing the " + command + " command."),
+ button);
+ }
+ }
+
+ // Enable the buttons
+ refreshButtons(editor);
+ return success;
+
+ }
+
+ // focus - sets focus to either the textarea or iframe
+ function focus(editor) {
+ setTimeout(function() {
+ if (sourceMode(editor)) editor.$area.focus();
+ else editor.$frame[0].contentWindow.focus();
+ refreshButtons(editor);
+ }, 0);
+ }
+
+ // getRange - gets the current text range object
+ function getRange(editor) {
+ if (ie) return getSelection(editor).createRange();
+ return getSelection(editor).getRangeAt(0);
+ }
+
+ // getSelection - gets the current text range object
+ function getSelection(editor) {
+ if (ie) return editor.doc.selection;
+ return editor.$frame[0].contentWindow.getSelection();
+ }
+
+ // Returns the hex value for the passed in string.
+ // hex("rgb(255, 0, 0)"); // #FF0000
+ // hex("#FF0000"); // #FF0000
+ // hex("#F00"); // #FF0000
+ function hex(s) {
+ var m = /rgba?\((\d+), (\d+), (\d+)/.exec(s),
+ c = s.split("");
+ if (m) {
+ s = ( m[1] << 16 | m[2] << 8 | m[3] ).toString(16);
+ while (s.length < 6)
+ s = "0" + s;
+ }
+ return "#" + (s.length == 6 ? s : c[1] + c[1] + c[2] + c[2] + c[3] + c[3]);
+ }
+
+ // hidePopups - hides all popups
+ function hidePopups() {
+ $.each(popups, function(idx, popup) {
+ $(popup)
+ .hide()
+ .unbind(CLICK)
+ .removeData(BUTTON);
+ });
+ }
+
+ // imagesPath - returns the path to the images folder
+ function imagesPath() {
+ var cssFile = "jquery.cleditor.css",
+ href = $("link[href$='" + cssFile +"']").attr("href");
+ return href.substr(0, href.length - cssFile.length) + "images/";
+ }
+
+ // imageUrl - Returns the css url string for a filemane
+ function imageUrl(filename) {
+ return "url(" + imagesPath() + filename + ")";
+ }
+
+ // refresh - creates the iframe and resizes the controls
+ function refresh(editor) {
+
+ var $main = editor.$main,
+ options = editor.options;
+
+ // Remove the old iframe
+ if (editor.$frame)
+ editor.$frame.remove();
+
+ // Create a new iframe
+ var $frame = editor.$frame = $('<iframe frameborder="0" src="javascript:true;">')
+ .hide()
+ .appendTo($main);
+
+ // Load the iframe document content
+ var contentWindow = $frame[0].contentWindow,
+ doc = editor.doc = contentWindow.document,
+ $doc = $(doc);
+
+ doc.open();
+ doc.write(
+ options.docType +
+ '<html>' +
+ ((options.docCSSFile === '') ? '' : '<head><link rel="stylesheet" type="text/css" href="' + options.docCSSFile + '" /></head>') +
+ '<body style="' + options.bodyStyle + '"></body></html>'
+ );
+ doc.close();
+
+ // Work around for bug in IE which causes the editor to lose
+ // focus when clicking below the end of the document.
+ if (ie)
+ $doc.click(function() {focus(editor);});
+
+ // Load the content
+ updateFrame(editor);
+
+ // Bind the ie specific iframe event handlers
+ if (ie) {
+
+ // Save the current user selection. This code is needed since IE will
+ // reset the selection just after the beforedeactivate event and just
+ // before the beforeactivate event.
+ $doc.bind("beforedeactivate beforeactivate selectionchange keypress", function(e) {
+
+ // Flag the editor as inactive
+ if (e.type == "beforedeactivate")
+ editor.inactive = true;
+
+ // Get rid of the bogus selection and flag the editor as active
+ else if (e.type == "beforeactivate") {
+ if (!editor.inactive && editor.range && editor.range.length > 1)
+ editor.range.shift();
+ delete editor.inactive;
+ }
+
+ // Save the selection when the editor is active
+ else if (!editor.inactive) {
+ if (!editor.range)
+ editor.range = [];
+ editor.range.unshift(getRange(editor));
+
+ // We only need the last 2 selections
+ while (editor.range.length > 2)
+ editor.range.pop();
+ }
+
+ });
+
+ // Restore the text range when the iframe gains focus
+ $frame.focus(function() {
+ restoreRange(editor);
+ });
+
+ }
+
+ // Update the textarea when the iframe loses focus
+ ($.browser.mozilla ? $doc : $(contentWindow)).blur(function() {
+ updateTextArea(editor, true);
+ });
+
+ // Enable the toolbar buttons as the user types or clicks
+ $doc.click(hidePopups)
+ .bind("keyup mouseup", function() {
+ refreshButtons(editor);
+ });
+
+ // Show the textarea for iPhone/iTouch/iPad or
+ // the iframe when design mode is supported.
+ if (iOS) editor.$area.show();
+ else $frame.show();
+
+ // Wait for the layout to finish - shortcut for $(document).ready()
+ $(function() {
+
+ var $toolbar = editor.$toolbar,
+ $group = $toolbar.children("div:last"),
+ wid = $main.width();
+
+ // Resize the toolbar
+ var hgt = $group.offset().top + $group.outerHeight() - $toolbar.offset().top + 1;
+ $toolbar.height(hgt);
+
+ // Resize the iframe
+ hgt = (/%/.test("" + options.height) ? $main.height() : parseInt(options.height)) - hgt;
+ $frame.width(wid).height(hgt);
+
+ // Resize the textarea. IE6 textareas have a 1px top
+ // & bottom margin that cannot be removed using css.
+ editor.$area.width(wid).height(ie6 ? hgt - 2 : hgt);
+
+ // Switch the iframe into design mode if enabled
+ disable(editor, editor.disabled);
+
+ // Enable or disable the toolbar buttons
+ refreshButtons(editor);
+
+ });
+
+ }
+
+ // refreshButtons - enables or disables buttons based on availability
+ function refreshButtons(editor) {
+
+ // Webkit requires focus before queryCommandEnabled will return anything but false
+ if (!iOS && $.browser.webkit && !editor.focused) {
+ editor.$frame[0].contentWindow.focus();
+ window.focus();
+ editor.focused = true;
+ }
+
+ // Get the object used for checking queryCommandEnabled
+ var queryObj = editor.doc;
+ if (ie) queryObj = getRange(editor);
+
+ // Loop through each button
+ var inSourceMode = sourceMode(editor);
+ $.each(editor.$toolbar.find("." + BUTTON_CLASS), function(idx, elem) {
+
+ var $elem = $(elem),
+ button = $.cleditor.buttons[$.data(elem, BUTTON_NAME)],
+ command = button.command,
+ enabled = true;
+
+ // Determine the state
+ if (editor.disabled)
+ enabled = false;
+ else if (button.getEnabled) {
+ var data = {
+ editor: editor,
+ button: elem,
+ buttonName: button.name,
+ popup: popups[button.popupName],
+ popupName: button.popupName,
+ command: button.command,
+ useCSS: editor.options.useCSS
+ };
+ enabled = button.getEnabled(data);
+ if (enabled === undefined)
+ enabled = true;
+ }
+ else if (((inSourceMode || iOS) && button.name != "source") ||
+ (ie && (command == "undo" || command == "redo")))
+ enabled = false;
+ else if (command && command != "print") {
+ if (ie && command == "hilitecolor")
+ command = "backcolor";
+ // IE does not support inserthtml, so it's always enabled
+ if (!ie || command != "inserthtml") {
+ try {enabled = queryObj.queryCommandEnabled(command);}
+ catch (err) {enabled = false;}
+ }
+ }
+
+ // Enable or disable the button
+ if (enabled) {
+ $elem.removeClass(DISABLED_CLASS);
+ $elem.removeAttr(DISABLED);
+ }
+ else {
+ $elem.addClass(DISABLED_CLASS);
+ $elem.attr(DISABLED, DISABLED);
+ }
+
+ });
+ }
+
+ // restoreRange - restores the current ie selection
+ function restoreRange(editor) {
+ if (ie && editor.range)
+ editor.range[0].select();
+ }
+
+ // select - selects all the text in either the textarea or iframe
+ function select(editor) {
+ setTimeout(function() {
+ if (sourceMode(editor)) editor.$area.select();
+ else execCommand(editor, "selectall");
+ }, 0);
+ }
+
+ // selectedHTML - returns the current HTML selection or and empty string
+ function selectedHTML(editor) {
+ restoreRange(editor);
+ var range = getRange(editor);
+ if (ie)
+ return range.htmlText;
+ var layer = $("<layer>")[0];
+ layer.appendChild(range.cloneContents());
+ var html = layer.innerHTML;
+ layer = null;
+ return html;
+ }
+
+ // selectedText - returns the current text selection or and empty string
+ function selectedText(editor) {
+ restoreRange(editor);
+ if (ie) return getRange(editor).text;
+ return getSelection(editor).toString();
+ }
+
+ // showMessage - alert replacement
+ function showMessage(editor, message, button) {
+ var popup = createPopup("msg", editor.options, MSG_CLASS);
+ popup.innerHTML = message;
+ showPopup(editor, popup, button);
+ }
+
+ // showPopup - shows a popup
+ function showPopup(editor, popup, button) {
+
+ var offset, left, top, $popup = $(popup);
+
+ // Determine the popup location
+ if (button) {
+ var $button = $(button);
+ offset = $button.offset();
+ left = --offset.left;
+ top = offset.top + $button.height();
+ }
+ else {
+ var $toolbar = editor.$toolbar;
+ offset = $toolbar.offset();
+ left = Math.floor(($toolbar.width() - $popup.width()) / 2) + offset.left;
+ top = offset.top + $toolbar.height() - 2;
+ }
+
+ // Position and show the popup
+ hidePopups();
+ $popup.css({left: left, top: top})
+ .show();
+
+ // Assign the popup button and click event handler
+ if (button) {
+ $.data(popup, BUTTON, button);
+ $popup.bind(CLICK, {popup: popup}, $.proxy(popupClick, editor));
+ }
+
+ // Focus the first input element if any
+ setTimeout(function() {
+ $popup.find(":text,textarea").eq(0).focus().select();
+ }, 100);
+
+ }
+
+ // sourceMode - returns true if the textarea is showing
+ function sourceMode(editor) {
+ return editor.$area.is(":visible");
+ }
+
+ // updateFrame - updates the iframe with the textarea contents
+ function updateFrame(editor, checkForChange) {
+
+ var code = editor.$area.val(),
+ options = editor.options,
+ updateFrameCallback = options.updateFrame,
+ $body = $(editor.doc.body);
+
+ // Check for textarea change to avoid unnecessary firing
+ // of potentially heavy updateFrame callbacks.
+ if (updateFrameCallback) {
+ var sum = checksum(code);
+ if (checkForChange && editor.areaChecksum == sum)
+ return;
+ editor.areaChecksum = sum;
+ }
+
+ // Convert the textarea source code into iframe html
+ var html = updateFrameCallback ? updateFrameCallback(code) : code;
+
+ // Prevent script injection attacks by html encoding script tags
+ html = html.replace(/<(?=\/?script)/ig, "<");
+
+ // Update the iframe checksum
+ if (options.updateTextArea)
+ editor.frameChecksum = checksum(html);
+
+ // Update the iframe and trigger the change event
+ if (html != $body.html()) {
+ $body.html(html);
+ $(editor).triggerHandler(CHANGE);
+ }
+
+ }
+
+ // updateTextArea - updates the textarea with the iframe contents
+ function updateTextArea(editor, checkForChange) {
+
+ var html = $(editor.doc.body).html(),
+ options = editor.options,
+ updateTextAreaCallback = options.updateTextArea,
+ $area = editor.$area;
+
+ // Check for iframe change to avoid unnecessary firing
+ // of potentially heavy updateTextArea callbacks.
+ if (updateTextAreaCallback) {
+ var sum = checksum(html);
+ if (checkForChange && editor.frameChecksum == sum)
+ return;
+ editor.frameChecksum = sum;
+ }
+
+ // Convert the iframe html into textarea source code
+ var code = updateTextAreaCallback ? updateTextAreaCallback(html) : html;
+
+ // Update the textarea checksum
+ if (options.updateFrame)
+ editor.areaChecksum = checksum(code);
+
+ // Update the textarea and trigger the change event
+ if (code != $area.val()) {
+ $area.val(code);
+ $(editor).triggerHandler(CHANGE);
+ }
+
+ }
+
+})(jQuery);
diff --git a/addons/web/static/lib/cleditor/jquery.cleditor.min.js b/addons/web/static/lib/cleditor/jquery.cleditor.min.js new file mode 100644 index 00000000000..5afec048867 --- /dev/null +++ b/addons/web/static/lib/cleditor/jquery.cleditor.min.js @@ -0,0 +1,31 @@ +/* + CLEditor WYSIWYG HTML Editor v1.3.0 + http://premiumsoftware.net/cleditor + requires jQuery v1.4.2 or later + + Copyright 2010, Chris Landowski, Premium Software, LLC + Dual licensed under the MIT or GPL Version 2 licenses. +*/ +(function(e){function aa(a){var b=this,c=a.target,d=e.data(c,x),h=s[d],f=h.popupName,i=p[f];if(!(b.disabled||e(c).attr(n)==n)){var g={editor:b,button:c,buttonName:d,popup:i,popupName:f,command:h.command,useCSS:b.options.useCSS};if(h.buttonClick&&h.buttonClick(a,g)===false)return false;if(d=="source"){if(t(b)){delete b.range;b.$area.hide();b.$frame.show();c.title=h.title}else{b.$frame.hide();b.$area.show();c.title="Show Rich Text"}setTimeout(function(){u(b)},100)}else if(!t(b))if(f){var j=e(i);if(f== +"url"){if(d=="link"&&M(b)===""){z(b,"A selection is required when inserting a link.",c);return false}j.children(":button").unbind(q).bind(q,function(){var k=j.find(":text"),o=e.trim(k.val());o!==""&&v(b,g.command,o,null,g.button);k.val("http://");r();w(b)})}else f=="pastetext"&&j.children(":button").unbind(q).bind(q,function(){var k=j.find("textarea"),o=k.val().replace(/\n/g,"<br />");o!==""&&v(b,g.command,o,null,g.button);k.val("");r();w(b)});if(c!==e.data(i,A)){N(b,i,c);return false}return}else if(d== +"print")b.$frame[0].contentWindow.print();else if(!v(b,g.command,g.value,g.useCSS,c))return false;w(b)}}function O(a){a=e(a.target).closest("div");a.css(H,a.data(x)?"#FFF":"#FFC")}function P(a){e(a.target).closest("div").css(H,"transparent")}function ba(a){var b=a.data.popup,c=a.target;if(!(b===p.msg||e(b).hasClass(B))){var d=e.data(b,A),h=e.data(d,x),f=s[h],i=f.command,g,j=this.options.useCSS;if(h=="font")g=c.style.fontFamily.replace(/"/g,"");else if(h=="size"){if(c.tagName=="DIV")c=c.children[0]; +g=c.innerHTML}else if(h=="style")g="<"+c.tagName+">";else if(h=="color")g=Q(c.style.backgroundColor);else if(h=="highlight"){g=Q(c.style.backgroundColor);if(l)i="backcolor";else j=true}b={editor:this,button:d,buttonName:h,popup:b,popupName:f.popupName,command:i,value:g,useCSS:j};if(!(f.popupClick&&f.popupClick(a,b)===false)){if(b.command&&!v(this,b.command,b.value,b.useCSS,d))return false;r();w(this)}}}function C(a){for(var b=1,c=0,d=0;d<a.length;++d){b=(b+a.charCodeAt(d))%65521;c=(c+b)%65521}return c<< +16|b}function R(a,b,c,d,h){if(p[a])return p[a];var f=e(m).hide().addClass(ca).appendTo("body");if(d)f.html(d);else if(a=="color"){b=b.colors.split(" ");b.length<10&&f.width("auto");e.each(b,function(i,g){e(m).appendTo(f).css(H,"#"+g)});c=da}else if(a=="font")e.each(b.fonts.split(","),function(i,g){e(m).appendTo(f).css("fontFamily",g).html(g)});else if(a=="size")e.each(b.sizes.split(","),function(i,g){e(m).appendTo(f).html("<font size="+g+">"+g+"</font>")});else if(a=="style")e.each(b.styles,function(i, +g){e(m).appendTo(f).html(g[1]+g[0]+g[1].replace("<","</"))});else if(a=="url"){f.html('Enter URL:<br><input type=text value="http://" size=35><br><input type=button value="Submit">');c=B}else if(a=="pastetext"){f.html("Paste your content here and click submit.<br /><textarea cols=40 rows=3></textarea><br /><input type=button value=Submit>");c=B}if(!c&&!d)c=S;f.addClass(c);l&&f.attr(I,"on").find("div,font,p,h1,h2,h3,h4,h5,h6").attr(I,"on");if(f.hasClass(S)||h===true)f.children().hover(O,P);p[a]=f[0]; +return f[0]}function T(a,b){if(b){a.$area.attr(n,n);a.disabled=true}else{a.$area.removeAttr(n);delete a.disabled}try{if(l)a.doc.body.contentEditable=!b;else a.doc.designMode=!b?"on":"off"}catch(c){}u(a)}function v(a,b,c,d,h){D(a);if(!l){if(d===undefined||d===null)d=a.options.useCSS;a.doc.execCommand("styleWithCSS",0,d.toString())}d=true;var f;if(l&&b.toLowerCase()=="inserthtml")y(a).pasteHTML(c);else{try{d=a.doc.execCommand(b,0,c||null)}catch(i){f=i.description;d=false}d||("cutcopypaste".indexOf(b)> +-1?z(a,"For security reasons, your browser does not support the "+b+" command. Try using the keyboard shortcut or context menu instead.",h):z(a,f?f:"Error executing the "+b+" command.",h))}u(a);return d}function w(a){setTimeout(function(){t(a)?a.$area.focus():a.$frame[0].contentWindow.focus();u(a)},0)}function y(a){if(l)return J(a).createRange();return J(a).getRangeAt(0)}function J(a){if(l)return a.doc.selection;return a.$frame[0].contentWindow.getSelection()}function Q(a){var b=/rgba?\((\d+), (\d+), (\d+)/.exec(a), +c=a.split("");if(b)for(a=(b[1]<<16|b[2]<<8|b[3]).toString(16);a.length<6;)a="0"+a;return"#"+(a.length==6?a:c[1]+c[1]+c[2]+c[2]+c[3]+c[3])}function r(){e.each(p,function(a,b){e(b).hide().unbind(q).removeData(A)})}function U(){var a=e("link[href$='jquery.cleditor.css']").attr("href");return a.substr(0,a.length-19)+"images/"}function K(a){var b=a.$main,c=a.options;a.$frame&&a.$frame.remove();var d=a.$frame=e('<iframe frameborder="0" src="javascript:true;">').hide().appendTo(b),h=d[0].contentWindow,f= +a.doc=h.document,i=e(f);f.open();f.write(c.docType+"<html>"+(c.docCSSFile===""?"":'<head><link rel="stylesheet" type="text/css" href="'+c.docCSSFile+'" /></head>')+'<body style="'+c.bodyStyle+'"></body></html>');f.close();l&&i.click(function(){w(a)});E(a);if(l){i.bind("beforedeactivate beforeactivate selectionchange keypress",function(g){if(g.type=="beforedeactivate")a.inactive=true;else if(g.type=="beforeactivate"){!a.inactive&&a.range&&a.range.length>1&&a.range.shift();delete a.inactive}else if(!a.inactive){if(!a.range)a.range= +[];for(a.range.unshift(y(a));a.range.length>2;)a.range.pop()}});d.focus(function(){D(a)})}(e.browser.mozilla?i:e(h)).blur(function(){V(a,true)});i.click(r).bind("keyup mouseup",function(){u(a)});L?a.$area.show():d.show();e(function(){var g=a.$toolbar,j=g.children("div:last"),k=b.width();j=j.offset().top+j.outerHeight()-g.offset().top+1;g.height(j);j=(/%/.test(""+c.height)?b.height():parseInt(c.height))-j;d.width(k).height(j);a.$area.width(k).height(ea?j-2:j);T(a,a.disabled);u(a)})}function u(a){if(!L&& +e.browser.webkit&&!a.focused){a.$frame[0].contentWindow.focus();window.focus();a.focused=true}var b=a.doc;if(l)b=y(a);var c=t(a);e.each(a.$toolbar.find("."+W),function(d,h){var f=e(h),i=e.cleditor.buttons[e.data(h,x)],g=i.command,j=true;if(a.disabled)j=false;else if(i.getEnabled){j=i.getEnabled({editor:a,button:h,buttonName:i.name,popup:p[i.popupName],popupName:i.popupName,command:i.command,useCSS:a.options.useCSS});if(j===undefined)j=true}else if((c||L)&&i.name!="source"||l&&(g=="undo"||g=="redo"))j= +false;else if(g&&g!="print"){if(l&&g=="hilitecolor")g="backcolor";if(!l||g!="inserthtml")try{j=b.queryCommandEnabled(g)}catch(k){j=false}}if(j){f.removeClass(X);f.removeAttr(n)}else{f.addClass(X);f.attr(n,n)}})}function D(a){l&&a.range&&a.range[0].select()}function M(a){D(a);if(l)return y(a).text;return J(a).toString()}function z(a,b,c){var d=R("msg",a.options,fa);d.innerHTML=b;N(a,d,c)}function N(a,b,c){var d,h,f=e(b);if(c){var i=e(c);d=i.offset();h=--d.left;d=d.top+i.height()}else{i=a.$toolbar; +d=i.offset();h=Math.floor((i.width()-f.width())/2)+d.left;d=d.top+i.height()-2}r();f.css({left:h,top:d}).show();if(c){e.data(b,A,c);f.bind(q,{popup:b},e.proxy(ba,a))}setTimeout(function(){f.find(":text,textarea").eq(0).focus().select()},100)}function t(a){return a.$area.is(":visible")}function E(a,b){var c=a.$area.val(),d=a.options,h=d.updateFrame,f=e(a.doc.body);if(h){var i=C(c);if(b&&a.areaChecksum==i)return;a.areaChecksum=i}c=h?h(c):c;c=c.replace(/<(?=\/?script)/ig,"<");if(d.updateTextArea)a.frameChecksum= +C(c);if(c!=f.html()){f.html(c);e(a).triggerHandler(F)}}function V(a,b){var c=e(a.doc.body).html(),d=a.options,h=d.updateTextArea,f=a.$area;if(h){var i=C(c);if(b&&a.frameChecksum==i)return;a.frameChecksum=i}c=h?h(c):c;if(d.updateFrame)a.areaChecksum=C(c);if(c!=f.val()){f.val(c);e(a).triggerHandler(F)}}e.cleditor={defaultOptions:{width:500,height:250,controls:"bold italic underline strikethrough subscript superscript | font size style | color highlight removeformat | bullets numbering | outdent indent | alignleft center alignright justify | undo redo | rule image link unlink | cut copy paste pastetext | print source", +colors:"FFF FCC FC9 FF9 FFC 9F9 9FF CFF CCF FCF CCC F66 F96 FF6 FF3 6F9 3FF 6FF 99F F9F BBB F00 F90 FC6 FF0 3F3 6CC 3CF 66C C6C 999 C00 F60 FC3 FC0 3C0 0CC 36F 63F C3C 666 900 C60 C93 990 090 399 33F 60C 939 333 600 930 963 660 060 366 009 339 636 000 300 630 633 330 030 033 006 309 303",fonts:"Arial,Arial Black,Comic Sans MS,Courier New,Narrow,Garamond,Georgia,Impact,Sans Serif,Serif,Tahoma,Trebuchet MS,Verdana",sizes:"1,2,3,4,5,6,7",styles:[["Paragraph","<p>"],["Header 1","<h1>"],["Header 2","<h2>"], +["Header 3","<h3>"],["Header 4","<h4>"],["Header 5","<h5>"],["Header 6","<h6>"]],useCSS:false,docType:'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">',docCSSFile:"",bodyStyle:"margin:4px; font:10pt Arial,Verdana; cursor:text"},buttons:{init:"bold,,|italic,,|underline,,|strikethrough,,|subscript,,|superscript,,|font,,fontname,|size,Font Size,fontsize,|style,,formatblock,|color,Font Color,forecolor,|highlight,Text Highlight Color,hilitecolor,color|removeformat,Remove Formatting,|bullets,,insertunorderedlist|numbering,,insertorderedlist|outdent,,|indent,,|alignleft,Align Text Left,justifyleft|center,,justifycenter|alignright,Align Text Right,justifyright|justify,,justifyfull|undo,,|redo,,|rule,Insert Horizontal Rule,inserthorizontalrule|image,Insert Image,insertimage,url|link,Insert Hyperlink,createlink,url|unlink,Remove Hyperlink,|cut,,|copy,,|paste,,|pastetext,Paste as Text,inserthtml,|print,,|source,Show Source"}, +imagesPath:function(){return U()}};e.fn.cleditor=function(a){var b=e([]);this.each(function(c,d){if(d.tagName=="TEXTAREA"){var h=e.data(d,Y);h||(h=new cleditor(d,a));b=b.add(h)}});return b};var H="backgroundColor",A="button",x="buttonName",F="change",Y="cleditor",q="click",n="disabled",m="<div>",I="unselectable",W="cleditorButton",X="cleditorDisabled",ca="cleditorPopup",S="cleditorList",da="cleditorColor",B="cleditorPrompt",fa="cleditorMsg",l=e.browser.msie,ea=/msie\s6/i.test(navigator.userAgent), +L=/iphone|ipad|ipod/i.test(navigator.userAgent),p={},Z,s=e.cleditor.buttons;e.each(s.init.split("|"),function(a,b){var c=b.split(","),d=c[0];s[d]={stripIndex:a,name:d,title:c[1]===""?d.charAt(0).toUpperCase()+d.substr(1):c[1],command:c[2]===""?d:c[2],popupName:c[3]===""?d:c[3]}});delete s.init;cleditor=function(a,b){var c=this;c.options=b=e.extend({},e.cleditor.defaultOptions,b);var d=c.$area=e(a).hide().data(Y,c).blur(function(){E(c,true)}),h=c.$main=e(m).addClass("cleditorMain").width(b.width).height(b.height), +f=c.$toolbar=e(m).addClass("cleditorToolbar").appendTo(h),i=e(m).addClass("cleditorGroup").appendTo(f);e.each(b.controls.split(" "),function(g,j){if(j==="")return true;if(j=="|"){e(m).addClass("cleditorDivider").appendTo(i);i=e(m).addClass("cleditorGroup").appendTo(f)}else{var k=s[j],o=e(m).data(x,k.name).addClass(W).attr("title",k.title).bind(q,e.proxy(aa,c)).appendTo(i).hover(O,P),G={};if(k.css)G=k.css;else if(k.image)G.backgroundImage="url("+U()+k.image+")";if(k.stripIndex)G.backgroundPosition= +k.stripIndex*-24;o.css(G);l&&o.attr(I,"on");k.popupName&&R(k.popupName,b,k.popupClass,k.popupContent,k.popupHover)}});h.insertBefore(d).append(d);if(!Z){e(document).click(function(g){g=e(g.target);g.add(g.parents()).is("."+B)||r()});Z=true}/auto|%/.test(""+b.width+b.height)&&e(window).resize(function(){K(c)});K(c)};var $=cleditor.prototype;e.each([["clear",function(a){a.$area.val("");E(a)}],["disable",T],["execCommand",v],["focus",w],["hidePopups",r],["sourceMode",t,true],["refresh",K],["select", +function(a){setTimeout(function(){t(a)?a.$area.select():v(a,"selectall")},0)}],["selectedHTML",function(a){D(a);a=y(a);if(l)return a.htmlText;var b=e("<layer>")[0];b.appendChild(a.cloneContents());return b.innerHTML},true],["selectedText",M,true],["showMessage",z],["updateFrame",E],["updateTextArea",V]],function(a,b){$[b[0]]=function(){for(var c=[this],d=0;d<arguments.length;d++)c.push(arguments[d]);c=b[1].apply(this,c);if(b[2])return c;return this}});$.change=function(a){var b=e(this);return a?b.bind(F, +a):b.trigger(F)}})(jQuery);
\ No newline at end of file diff --git a/addons/web/static/lib/jquery.autosize/jquery.autosize.js b/addons/web/static/lib/jquery.autosize/jquery.autosize.js new file mode 100644 index 00000000000..9d972e96a57 --- /dev/null +++ b/addons/web/static/lib/jquery.autosize/jquery.autosize.js @@ -0,0 +1,165 @@ +// Autosize 1.10 - jQuery plugin for textareas
+// (c) 2012 Jack Moore - jacklmoore.com
+// license: www.opensource.org/licenses/mit-license.php
+
+(function ($) {
+ var
+ hidden = 'hidden',
+ borderBox = 'border-box',
+ lineHeight = 'lineHeight',
+ copy = '<textarea tabindex="-1" style="position:absolute; top:-9999px; left:-9999px; right:auto; bottom:auto; -moz-box-sizing:content-box; -webkit-box-sizing:content-box; box-sizing:content-box; word-wrap:break-word; height:0 !important; min-height:0 !important; overflow:hidden">',
+ // line-height is omitted because IE7/IE8 doesn't return the correct value.
+ copyStyle = [
+ 'fontFamily',
+ 'fontSize',
+ 'fontWeight',
+ 'fontStyle',
+ 'letterSpacing',
+ 'textTransform',
+ 'wordSpacing',
+ 'textIndent'
+ ],
+ oninput = 'oninput',
+ onpropertychange = 'onpropertychange',
+ test = $(copy)[0];
+
+ // For testing support in old FireFox
+ test.setAttribute(oninput, "return");
+
+ if ($.isFunction(test[oninput]) || onpropertychange in test) {
+
+ // test that line-height can be accurately copied to avoid
+ // incorrect value reporting in old IE and old Opera
+ $(test).css(lineHeight, '99px');
+ if ($(test).css(lineHeight) === '99px') {
+ copyStyle.push(lineHeight);
+ }
+
+ $.fn.autosize = function (className) {
+ return this.each(function () {
+ var
+ ta = this,
+ $ta = $(ta),
+ mirror,
+ minHeight = $ta.height(),
+ maxHeight = parseInt($ta.css('maxHeight'), 10),
+ active,
+ i = copyStyle.length,
+ resize,
+ boxOffset = 0;
+
+ if ($ta.css('box-sizing') === borderBox || $ta.css('-moz-box-sizing') === borderBox || $ta.css('-webkit-box-sizing') === borderBox){
+ boxOffset = $ta.outerHeight() - $ta.height();
+ }
+
+ if ($ta.data('mirror') || $ta.data('ismirror')) {
+ // if autosize has already been applied, exit.
+ // if autosize is being applied to a mirror element, exit.
+ return;
+ } else {
+ mirror = $(copy).data('ismirror', true).addClass(className || 'autosizejs')[0];
+
+ // Changed allowed resize only in edit mode by VTA (31/8/2012)
+ if ($ta.attr('disabled')) {
+ $ta.css('resize', 'none');
+ }
+ // Changed horizontal to vertical by VTA (31/8/2012)
+ resize = $ta.css('resize') === 'none' ? 'none' : 'vertical';
+
+ $ta.data('mirror', $(mirror)).css({
+ overflow: hidden,
+ overflowY: hidden,
+ wordWrap: 'break-word',
+ resize: resize
+ });
+ }
+
+ // Opera returns '-1px' when max-height is set to 'none'.
+ maxHeight = maxHeight && maxHeight > 0 ? maxHeight : 9e4;
+
+ // Using mainly bare JS in this function because it is going
+ // to fire very often while typing, and needs to very efficient.
+ function adjust() {
+ var height, overflow;
+ // the active flag keeps IE from tripping all over itself. Otherwise
+ // actions in the adjust function will cause IE to call adjust again.
+ if (!active) {
+ active = true;
+
+ mirror.value = ta.value;
+
+ mirror.style.overflowY = ta.style.overflowY;
+
+ // Update the width in case the original textarea width has changed
+ mirror.style.width = $ta.css('width');
+
+ // Needed for IE to reliably return the correct scrollHeight
+ mirror.scrollTop = 0;
+
+ // Set a very high value for scrollTop to be sure the
+ // mirror is scrolled all the way to the bottom.
+ mirror.scrollTop = 9e4;
+
+ height = mirror.scrollTop;
+ overflow = hidden;
+ if (height > maxHeight) {
+ height = maxHeight;
+ overflow = 'scroll';
+ } else if (height < minHeight) {
+ height = minHeight;
+ }
+ ta.style.overflowY = overflow;
+
+ ta.style.height = height + boxOffset + 'px';
+
+ // This small timeout gives IE a chance to draw it's scrollbar
+ // before adjust can be run again (prevents an infinite loop).
+ setTimeout(function () {
+ active = false;
+ }, 1);
+ }
+ }
+
+ // mirror is a duplicate textarea located off-screen that
+ // is automatically updated to contain the same text as the
+ // original textarea. mirror always has a height of 0.
+ // This gives a cross-browser supported way getting the actual
+ // height of the text, through the scrollTop property.
+ while (i--) {
+ mirror.style[copyStyle[i]] = $ta.css(copyStyle[i]);
+ }
+
+ $('body').append(mirror);
+
+ if (onpropertychange in ta) {
+ if (oninput in ta) {
+ // Detects IE9. IE9 does not fire onpropertychange or oninput for deletions,
+ // so binding to onkeyup to catch most of those occassions. There is no way that I
+ // know of to detect something like 'cut' in IE9.
+ ta[oninput] = ta.onkeyup = adjust;
+ } else {
+ // IE7 / IE8
+ ta[onpropertychange] = adjust;
+ }
+ } else {
+ // Modern Browsers
+ ta[oninput] = adjust;
+ }
+
+ $(window).resize(adjust);
+
+ // Allow for manual triggering if needed.
+ $ta.bind('autosize', adjust);
+
+ // Call adjust in case the textarea already contains text.
+ adjust();
+ });
+ };
+ } else {
+ // Makes no changes for older browsers (FireFox3- and Safari4-)
+ $.fn.autosize = function () {
+ return this;
+ };
+ }
+
+}(jQuery));
\ No newline at end of file diff --git a/addons/web/static/lib/jquery.superfish/changelog.txt b/addons/web/static/lib/jquery.superfish/changelog.txt deleted file mode 100644 index 3399f057ec3..00000000000 --- a/addons/web/static/lib/jquery.superfish/changelog.txt +++ /dev/null @@ -1,74 +0,0 @@ -Changelog for Superfish - a jQuery menu plugin - -v1.2.1: altered 2nd July 07. added hide() before animate to make work for jQuery 1.1.3. - -v1.2.2: altered 2nd August 07. changed over function .find('ul') to .find('>ul') for smoother animations - Also deleted the iframe removal lines - not necessary it turns out - -v1.2.3: altered jquery 1.1.3.1 broke keyboard access - had to change quite a few things and set display:none on the - .superfish rule in CSS instead of top:-999em - -v1.3: Pretty much a complete overhaul to make all original features work in 1.1.3.1 and above. - .superfish rule reverted back to top:-999em (which is better). - -v1.3.1: altered 'li[ul]' to $('li:has(ul)') to work with jQuery 1.2 - -v1.3.2: added onshow callback option as requested - 'this' keyword refers to revealed ul. - fixed bug whereby multiple menus on a page shared options. Now each menu can have separate options. - fixed IE6 and IE7 bug whereby under certain circumstances => 3rd tier menus appear instantly with text missing when revisited - -v1.3.3: altered event attachment selectors for performance increase on menu setup. - -v1.3.4: fixed pathClass bug as current path was not being restored. Still doesn't if using keyboard nav (will work on that). - -v1.4: store options objects in array $.superfish.o. Also provide public access to $.superfish.defaults - provided backward compat for jQuery versions less than 1.2 via oldJquery option - will use li[ul] or li:has(ul) as needed - added more callbacks, also added option to disable hoverIntent usage if that plugin is detected - -v1.4.1: fixed current path not being restored when using keyboard to tab away from the menu completely - optimised code further - now less code - removed addself() in favour of backward compatible add(this) - also remove new mouseenter/mouseleave events on window.unload (due to paranoia) - -v1.4.2: 3rd July 2008. added semi-colon at start of superfish.js file to avert script concatenation errors - added pathLevels option to limit the depth of submenus that get restored by pathClass - CSS of main example hugely simplified - other example CSS files less-so. - - top level menu items are not fixed width - - only need to set submenu widths in one place. - - various other improvements. - - class names used in all CSS files are changed to be less generic - released optional Supersubs plugin which dynamically adjusts submenu widths - attach everything to jQuery.fn.superfish to clean up jQuery namespace - removed the multi-tier-all-horizontal-example as it was never a great idea and doesn't seem to be in use - Update documentation text and remove certain caveats which were there to support users of much older versions of Superfish - Documentation now show how to apply bgIgrame 2.1 - it's much easier than before - Add all links and their focus and blur events to the onunload unbind - paranoid about memory leaks - -v1.4.3 5th July 2008. documentation completely redone using Mike Alsup's templates. - CSS revised so that all types of menu use superfish.css as a base. Include additional CSS files to acheive alternate styles. - class="sf-menu sf-vertical" creates vertical menu, class="sf-menu sf-navbar" creates nav-bar style menu - -v1.4.4 7th July 2008. arrows feature added. If option 'autoArrows' is true, auto-appends (spans) to anchors that have a ul submenu sibling, - and adds class 'sf-with-ul' to anchor. - CSS added to style new arrows, including an 8-bit alpha (NOT INDEXED!) transparent png of arrow - degrades to solid for IE6. - Manually add arrow mark-up to docs and disable autoArrows (via defaults option) to maintain decent performance (for docs page) - Update docs, including zip download, to suit. - Fix CSS bug that had the third tier of the navbar-style menu visible when JS not available. - -v1.4.5 9 July 2008. decreased code weight - added drop shadows for capable browsers - added css and 8-bit alpha transparent png image for shadow - remove support for jQuery v<1.2 - remove unload clean-up which was there to solve garbage collection problems in early jQuery versions - remove toggling 'visibility' on hide and show which as a fix for an IE bug that is no longer exhibited - removed need for getOpts, rewrote getMenu - use [].join('') instead of string concatenation for performance increase - probably very slight in this case - change selector in 'over' function from '.'+o.hoverClass to 'li.'+o.hoverClass - -v1.4.6 added workaround for IE7 shadows. If animation alters opacity then shadow is hidden during the animation and appears after. - This required some JS and a line of CSS, so created shortcut references to lighten code further. - -v1.4.7 added back in the visibility toggle that was removed in 1.4.5 as the bug is indeed still exhibited by IE6 & 7 - tweaked the look of the shadow slightly and use nicer 32bit png as I now find they behave the same as 8bit png with regard to fades in IE7 - -v1.4.8 fix over-sight: using 32bit png meant that the shadow image did show in IE6. Rather than go back to 8bit, add code to exclude IE6 from getting shadow class. - use new folder structure recommended by Matt from TopicTrack, for easier updates.
\ No newline at end of file diff --git a/addons/web/static/lib/jquery.superfish/css/superfish-navbar.css b/addons/web/static/lib/jquery.superfish/css/superfish-navbar.css deleted file mode 100644 index 68c713584aa..00000000000 --- a/addons/web/static/lib/jquery.superfish/css/superfish-navbar.css +++ /dev/null @@ -1,93 +0,0 @@ - -/*** adding the class sf-navbar in addition to sf-menu creates an all-horizontal nav-bar menu ***/ -.sf-navbar { - background: #BDD2FF; - height: 2.5em; - padding-bottom: 2.5em; - position: relative; -} -.sf-navbar li { - background: #AABDE6; - position: static; -} -.sf-navbar a { - border-top: none; -} -.sf-navbar li ul { - width: 44em; /*IE6 soils itself without this*/ -} -.sf-navbar li li { - background: #BDD2FF; - position: relative; -} -.sf-navbar li li ul { - width: 13em; -} -.sf-navbar li li li { - width: 100%; -} -.sf-navbar ul li { - width: auto; - float: left; -} -.sf-navbar a, .sf-navbar a:visited { - border: none; -} -.sf-navbar li.current { - background: #BDD2FF; -} -.sf-navbar li:hover, -.sf-navbar li.sfHover, -.sf-navbar li li.current, -.sf-navbar a:focus, .sf-navbar a:hover, .sf-navbar a:active { - background: #BDD2FF; -} -.sf-navbar ul li:hover, -.sf-navbar ul li.sfHover, -ul.sf-navbar ul li:hover li, -ul.sf-navbar ul li.sfHover li, -.sf-navbar ul a:focus, .sf-navbar ul a:hover, .sf-navbar ul a:active { - background: #D1DFFF; -} -ul.sf-navbar li li li:hover, -ul.sf-navbar li li li.sfHover, -.sf-navbar li li.current li.current, -.sf-navbar ul li li a:focus, .sf-navbar ul li li a:hover, .sf-navbar ul li li a:active { - background: #E6EEFF; -} -ul.sf-navbar .current ul, -ul.sf-navbar ul li:hover ul, -ul.sf-navbar ul li.sfHover ul { - left: 0; - top: 2.5em; /* match top ul list item height */ -} -ul.sf-navbar .current ul ul { - top: -999em; -} - -.sf-navbar li li.current > a { - font-weight: bold; -} - -/*** point all arrows down ***/ -/* point right for anchors in subs */ -.sf-navbar ul .sf-sub-indicator { background-position: -10px -100px; } -.sf-navbar ul a > .sf-sub-indicator { background-position: 0 -100px; } -/* apply hovers to modern browsers */ -.sf-navbar ul a:focus > .sf-sub-indicator, -.sf-navbar ul a:hover > .sf-sub-indicator, -.sf-navbar ul a:active > .sf-sub-indicator, -.sf-navbar ul li:hover > a > .sf-sub-indicator, -.sf-navbar ul li.sfHover > a > .sf-sub-indicator { - background-position: -10px -100px; /* arrow hovers for modern browsers*/ -} - -/*** remove shadow on first submenu ***/ -.sf-navbar > li > ul { - background: transparent; - padding: 0; - -moz-border-radius-bottomleft: 0; - -moz-border-radius-topright: 0; - -webkit-border-top-right-radius: 0; - -webkit-border-bottom-left-radius: 0; -}
\ No newline at end of file diff --git a/addons/web/static/lib/jquery.superfish/css/superfish-vertical.css b/addons/web/static/lib/jquery.superfish/css/superfish-vertical.css deleted file mode 100644 index 8025b7859f6..00000000000 --- a/addons/web/static/lib/jquery.superfish/css/superfish-vertical.css +++ /dev/null @@ -1,23 +0,0 @@ -/*** adding sf-vertical in addition to sf-menu creates a vertical menu ***/ -.sf-vertical, .sf-vertical li { - width: 10em; -} -/* this lacks ul at the start of the selector, so the styles from the main CSS file override it where needed */ -.sf-vertical li:hover ul, -.sf-vertical li.sfHover ul { - left: 10em; /* match ul width */ - top: 0; -} - -/*** alter arrow directions ***/ -.sf-vertical .sf-sub-indicator { background-position: -10px 0; } /* IE6 gets solid image only */ -.sf-vertical a > .sf-sub-indicator { background-position: 0 0; } /* use translucent arrow for modern browsers*/ - -/* hover arrow direction for modern browsers*/ -.sf-vertical a:focus > .sf-sub-indicator, -.sf-vertical a:hover > .sf-sub-indicator, -.sf-vertical a:active > .sf-sub-indicator, -.sf-vertical li:hover > a > .sf-sub-indicator, -.sf-vertical li.sfHover > a > .sf-sub-indicator { - background-position: -10px 0; /* arrow hovers for modern browsers*/ -}
\ No newline at end of file diff --git a/addons/web/static/lib/jquery.superfish/css/superfish.css b/addons/web/static/lib/jquery.superfish/css/superfish.css deleted file mode 100644 index cc33fdb4e7f..00000000000 --- a/addons/web/static/lib/jquery.superfish/css/superfish.css +++ /dev/null @@ -1,136 +0,0 @@ - -/*** ESSENTIAL STYLES ***/ -.sf-menu, .sf-menu * { - margin: 0; - padding: 0; - list-style: none; -} -.sf-menu { - line-height: 1.0; -} -.sf-menu ul { - position: absolute; - top: -999em; - width: 10em; /* left offset of submenus need to match (see below) */ -} -.sf-menu ul li { - width: 100%; -} -.sf-menu li:hover { - visibility: inherit; /* fixes IE7 'sticky bug' */ -} -.sf-menu li { - float: left; - position: relative; -} -.sf-menu a { - display: block; - position: relative; -} -.sf-menu li:hover ul, -.sf-menu li.sfHover ul { - left: 0; - top: 2.5em; /* match top ul list item height */ - z-index: 99; -} -ul.sf-menu li:hover li ul, -ul.sf-menu li.sfHover li ul { - top: -999em; -} -ul.sf-menu li li:hover ul, -ul.sf-menu li li.sfHover ul { - left: 10em; /* match ul width */ - top: 0; -} -ul.sf-menu li li:hover li ul, -ul.sf-menu li li.sfHover li ul { - top: -999em; -} -ul.sf-menu li li li:hover ul, -ul.sf-menu li li li.sfHover ul { - left: 10em; /* match ul width */ - top: 0; -} - -/*** DEMO SKIN ***/ -.sf-menu { - float: left; - margin-bottom: 1em; -} -.sf-menu a { - border-left: 1px solid #fff; - border-top: 1px solid #CFDEFF; - padding: .75em 1em; - text-decoration:none; -} -.sf-menu a, .sf-menu a:visited { /* visited pseudo selector so IE6 applies text colour*/ - color: #13a; -} -.sf-menu li { - background: #BDD2FF; -} -.sf-menu li li { - background: #AABDE6; -} -.sf-menu li li li { - background: #9AAEDB; -} -.sf-menu li:hover, .sf-menu li.sfHover, -.sf-menu a:focus, .sf-menu a:hover, .sf-menu a:active { - background: #CFDEFF; - outline: 0; -} - -/*** arrows **/ -.sf-menu a.sf-with-ul { - padding-right: 2.25em; - min-width: 1px; /* trigger IE7 hasLayout so spans position accurately */ -} -.sf-sub-indicator { - position: absolute; - display: block; - right: .75em; - top: 1.05em; /* IE6 only */ - width: 10px; - height: 10px; - text-indent: -999em; - overflow: hidden; - background: url('../images/arrows-ffffff.png') no-repeat -10px -100px; /* 8-bit indexed alpha png. IE6 gets solid image only */ -} -a > .sf-sub-indicator { /* give all except IE6 the correct values */ - top: .8em; - background-position: 0 -100px; /* use translucent arrow for modern browsers*/ -} -/* apply hovers to modern browsers */ -a:focus > .sf-sub-indicator, -a:hover > .sf-sub-indicator, -a:active > .sf-sub-indicator, -li:hover > a > .sf-sub-indicator, -li.sfHover > a > .sf-sub-indicator { - background-position: -10px -100px; /* arrow hovers for modern browsers*/ -} - -/* point right for anchors in subs */ -.sf-menu ul .sf-sub-indicator { background-position: -10px 0; } -.sf-menu ul a > .sf-sub-indicator { background-position: 0 0; } -/* apply hovers to modern browsers */ -.sf-menu ul a:focus > .sf-sub-indicator, -.sf-menu ul a:hover > .sf-sub-indicator, -.sf-menu ul a:active > .sf-sub-indicator, -.sf-menu ul li:hover > a > .sf-sub-indicator, -.sf-menu ul li.sfHover > a > .sf-sub-indicator { - background-position: -10px 0; /* arrow hovers for modern browsers*/ -} - -/*** shadows for all but IE6 ***/ -.sf-shadow ul { - background: url('../images/shadow.png') no-repeat bottom right; - padding: 0 8px 9px 0; - -moz-border-radius-bottomleft: 17px; - -moz-border-radius-topright: 17px; - -webkit-border-top-right-radius: 17px; - -webkit-border-bottom-left-radius: 17px; -} -.sf-shadow ul.sf-shadow-off { - background: transparent; -} diff --git a/addons/web/static/lib/jquery.superfish/example.html b/addons/web/static/lib/jquery.superfish/example.html deleted file mode 100644 index 58205271719..00000000000 --- a/addons/web/static/lib/jquery.superfish/example.html +++ /dev/null @@ -1,123 +0,0 @@ -<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" -"http://www.w3.org/TR/html4/strict.dtd"> -<html lang="en"> - <head> - <title>A very basic Superfish menu example</title> - <meta http-equiv="content-type" content="text/html;charset=utf-8"> - <link rel="stylesheet" type="text/css" href="css/superfish.css" media="screen"> - <script type="text/javascript" src="js/jquery-1.2.6.min.js"></script> - <script type="text/javascript" src="js/hoverIntent.js"></script> - <script type="text/javascript" src="js/superfish.js"></script> - <script type="text/javascript"> - - // initialise plugins - jQuery(function(){ - jQuery('ul.sf-menu').superfish(); - }); - - </script> - </head> - <body> - <ul class="sf-menu"> - <li class="current"> - <a href="#a">menu item</a> - <ul> - <li> - <a href="#aa">menu item that is quite long</a> - </li> - <li class="current"> - <a href="#ab">menu item</a> - <ul> - <li class="current"><a href="#">menu item</a></li> - <li><a href="#aba">menu item</a></li> - <li><a href="#abb">menu item</a></li> - <li><a href="#abc">menu item</a></li> - <li><a href="#abd">menu item</a></li> - </ul> - </li> - <li> - <a href="#">menu item</a> - <ul> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - </ul> - </li> - <li> - <a href="#">menu item</a> - <ul> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - </ul> - </li> - </ul> - </li> - <li> - <a href="#">menu item</a> - </li> - <li> - <a href="#">menu item</a> - <ul> - <li> - <a href="#">menu item</a> - <ul> - <li><a href="#">short</a></li> - <li><a href="#">short</a></li> - <li><a href="#">short</a></li> - <li><a href="#">short</a></li> - <li><a href="#">short</a></li> - </ul> - </li> - <li> - <a href="#">menu item</a> - <ul> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - </ul> - </li> - <li> - <a href="#">menu item</a> - <ul> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - </ul> - </li> - <li> - <a href="#">menu item</a> - <ul> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - </ul> - </li> - <li> - <a href="#">menu item</a> - <ul> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - <li><a href="#">menu item</a></li> - </ul> - </li> - </ul> - </li> - <li> - <a href="#">menu item</a> - </li> - </ul> - </body> -</html>
\ No newline at end of file diff --git a/addons/web/static/lib/jquery.superfish/images/arrows-ffffff.png b/addons/web/static/lib/jquery.superfish/images/arrows-ffffff.png Binary files differdeleted file mode 100644 index 995df52065b..00000000000 --- a/addons/web/static/lib/jquery.superfish/images/arrows-ffffff.png +++ /dev/null diff --git a/addons/web/static/lib/jquery.superfish/images/shadow.png b/addons/web/static/lib/jquery.superfish/images/shadow.png Binary files differdeleted file mode 100644 index c04d21b76ec..00000000000 --- a/addons/web/static/lib/jquery.superfish/images/shadow.png +++ /dev/null diff --git a/addons/web/static/lib/jquery.superfish/js/hoverIntent.js b/addons/web/static/lib/jquery.superfish/js/hoverIntent.js deleted file mode 100644 index 91da57b2448..00000000000 --- a/addons/web/static/lib/jquery.superfish/js/hoverIntent.js +++ /dev/null @@ -1,84 +0,0 @@ -(function($){ - /* hoverIntent by Brian Cherne */ - $.fn.hoverIntent = function(f,g) { - // default configuration options - var cfg = { - sensitivity: 7, - interval: 100, - timeout: 0 - }; - // override configuration options with user supplied object - cfg = $.extend(cfg, g ? { over: f, out: g } : f ); - - // instantiate variables - // cX, cY = current X and Y position of mouse, updated by mousemove event - // pX, pY = previous X and Y position of mouse, set by mouseover and polling interval - var cX, cY, pX, pY; - - // A private function for getting mouse position - var track = function(ev) { - cX = ev.pageX; - cY = ev.pageY; - }; - - // A private function for comparing current and previous mouse position - var compare = function(ev,ob) { - ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); - // compare mouse positions to see if they've crossed the threshold - if ( ( Math.abs(pX-cX) + Math.abs(pY-cY) ) < cfg.sensitivity ) { - $(ob).unbind("mousemove",track); - // set hoverIntent state to true (so mouseOut can be called) - ob.hoverIntent_s = 1; - return cfg.over.apply(ob,[ev]); - } else { - // set previous coordinates for next time - pX = cX; pY = cY; - // use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs) - ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval ); - } - }; - - // A private function for delaying the mouseOut function - var delay = function(ev,ob) { - ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); - ob.hoverIntent_s = 0; - return cfg.out.apply(ob,[ev]); - }; - - // A private function for handling mouse 'hovering' - var handleHover = function(e) { - // next three lines copied from jQuery.hover, ignore children onMouseOver/onMouseOut - var p = (e.type == "mouseover" ? e.fromElement : e.toElement) || e.relatedTarget; - while ( p && p != this ) { try { p = p.parentNode; } catch(e) { p = this; } } - if ( p == this ) { return false; } - - // copy objects to be passed into t (required for event object to be passed in IE) - var ev = jQuery.extend({},e); - var ob = this; - - // cancel hoverIntent timer if it exists - if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); } - - // else e.type == "onmouseover" - if (e.type == "mouseover") { - // set "previous" X and Y position based on initial entry point - pX = ev.pageX; pY = ev.pageY; - // update "current" X and Y position based on mousemove - $(ob).bind("mousemove",track); - // start polling interval (self-calling timeout) to compare mouse coordinates over time - if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );} - - // else e.type == "onmouseout" - } else { - // unbind expensive mousemove event - $(ob).unbind("mousemove",track); - // if hoverIntent state is true, then call the mouseOut function after the specified delay - if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );} - } - }; - - // bind the function to the two event listeners - return this.mouseover(handleHover).mouseout(handleHover); - }; - -})(jQuery);
\ No newline at end of file diff --git a/addons/web/static/lib/jquery.superfish/js/jquery-1.2.6.min.js b/addons/web/static/lib/jquery.superfish/js/jquery-1.2.6.min.js deleted file mode 100644 index 82b98e1d766..00000000000 --- a/addons/web/static/lib/jquery.superfish/js/jquery-1.2.6.min.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * jQuery 1.2.6 - New Wave Javascript - * - * Copyright (c) 2008 John Resig (jquery.com) - * Dual licensed under the MIT (MIT-LICENSE.txt) - * and GPL (GPL-LICENSE.txt) licenses. - * - * $Date: 2008-05-24 14:22:17 -0400 (Sat, 24 May 2008) $ - * $Rev: 5685 $ - */ -(function(){var _jQuery=window.jQuery,_$=window.$;var jQuery=window.jQuery=window.$=function(selector,context){return new jQuery.fn.init(selector,context);};var quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/,isSimple=/^.[^:#\[\.]*$/,undefined;jQuery.fn=jQuery.prototype={init:function(selector,context){selector=selector||document;if(selector.nodeType){this[0]=selector;this.length=1;return this;}if(typeof selector=="string"){var match=quickExpr.exec(selector);if(match&&(match[1]||!context)){if(match[1])selector=jQuery.clean([match[1]],context);else{var elem=document.getElementById(match[3]);if(elem){if(elem.id!=match[3])return jQuery().find(selector);return jQuery(elem);}selector=[];}}else -return jQuery(context).find(selector);}else if(jQuery.isFunction(selector))return jQuery(document)[jQuery.fn.ready?"ready":"load"](selector);return this.setArray(jQuery.makeArray(selector));},jquery:"1.2.6",size:function(){return this.length;},length:0,get:function(num){return num==undefined?jQuery.makeArray(this):this[num];},pushStack:function(elems){var ret=jQuery(elems);ret.prevObject=this;return ret;},setArray:function(elems){this.length=0;Array.prototype.push.apply(this,elems);return this;},each:function(callback,args){return jQuery.each(this,callback,args);},index:function(elem){var ret=-1;return jQuery.inArray(elem&&elem.jquery?elem[0]:elem,this);},attr:function(name,value,type){var options=name;if(name.constructor==String)if(value===undefined)return this[0]&&jQuery[type||"attr"](this[0],name);else{options={};options[name]=value;}return this.each(function(i){for(name in options)jQuery.attr(type?this.style:this,name,jQuery.prop(this,options[name],type,i,name));});},css:function(key,value){if((key=='width'||key=='height')&&parseFloat(value)<0)value=undefined;return this.attr(key,value,"curCSS");},text:function(text){if(typeof text!="object"&&text!=null)return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(text));var ret="";jQuery.each(text||this,function(){jQuery.each(this.childNodes,function(){if(this.nodeType!=8)ret+=this.nodeType!=1?this.nodeValue:jQuery.fn.text([this]);});});return ret;},wrapAll:function(html){if(this[0])jQuery(html,this[0].ownerDocument).clone().insertBefore(this[0]).map(function(){var elem=this;while(elem.firstChild)elem=elem.firstChild;return elem;}).append(this);return this;},wrapInner:function(html){return this.each(function(){jQuery(this).contents().wrapAll(html);});},wrap:function(html){return this.each(function(){jQuery(this).wrapAll(html);});},append:function(){return this.domManip(arguments,true,false,function(elem){if(this.nodeType==1)this.appendChild(elem);});},prepend:function(){return this.domManip(arguments,true,true,function(elem){if(this.nodeType==1)this.insertBefore(elem,this.firstChild);});},before:function(){return this.domManip(arguments,false,false,function(elem){this.parentNode.insertBefore(elem,this);});},after:function(){return this.domManip(arguments,false,true,function(elem){this.parentNode.insertBefore(elem,this.nextSibling);});},end:function(){return this.prevObject||jQuery([]);},find:function(selector){var elems=jQuery.map(this,function(elem){return jQuery.find(selector,elem);});return this.pushStack(/[^+>] [^+>]/.test(selector)||selector.indexOf("..")>-1?jQuery.unique(elems):elems);},clone:function(events){var ret=this.map(function(){if(jQuery.browser.msie&&!jQuery.isXMLDoc(this)){var clone=this.cloneNode(true),container=document.createElement("div");container.appendChild(clone);return jQuery.clean([container.innerHTML])[0];}else -return this.cloneNode(true);});var clone=ret.find("*").andSelf().each(function(){if(this[expando]!=undefined)this[expando]=null;});if(events===true)this.find("*").andSelf().each(function(i){if(this.nodeType==3)return;var events=jQuery.data(this,"events");for(var type in events)for(var handler in events[type])jQuery.event.add(clone[i],type,events[type][handler],events[type][handler].data);});return ret;},filter:function(selector){return this.pushStack(jQuery.isFunction(selector)&&jQuery.grep(this,function(elem,i){return selector.call(elem,i);})||jQuery.multiFilter(selector,this));},not:function(selector){if(selector.constructor==String)if(isSimple.test(selector))return this.pushStack(jQuery.multiFilter(selector,this,true));else -selector=jQuery.multiFilter(selector,this);var isArrayLike=selector.length&&selector[selector.length-1]!==undefined&&!selector.nodeType;return this.filter(function(){return isArrayLike?jQuery.inArray(this,selector)<0:this!=selector;});},add:function(selector){return this.pushStack(jQuery.unique(jQuery.merge(this.get(),typeof selector=='string'?jQuery(selector):jQuery.makeArray(selector))));},is:function(selector){return!!selector&&jQuery.multiFilter(selector,this).length>0;},hasClass:function(selector){return this.is("."+selector);},val:function(value){if(value==undefined){if(this.length){var elem=this[0];if(jQuery.nodeName(elem,"select")){var index=elem.selectedIndex,values=[],options=elem.options,one=elem.type=="select-one";if(index<0)return null;for(var i=one?index:0,max=one?index+1:options.length;i<max;i++){var option=options[i];if(option.selected){value=jQuery.browser.msie&&!option.attributes.value.specified?option.text:option.value;if(one)return value;values.push(value);}}return values;}else -return(this[0].value||"").replace(/\r/g,"");}return undefined;}if(value.constructor==Number)value+='';return this.each(function(){if(this.nodeType!=1)return;if(value.constructor==Array&&/radio|checkbox/.test(this.type))this.checked=(jQuery.inArray(this.value,value)>=0||jQuery.inArray(this.name,value)>=0);else if(jQuery.nodeName(this,"select")){var values=jQuery.makeArray(value);jQuery("option",this).each(function(){this.selected=(jQuery.inArray(this.value,values)>=0||jQuery.inArray(this.text,values)>=0);});if(!values.length)this.selectedIndex=-1;}else -this.value=value;});},html:function(value){return value==undefined?(this[0]?this[0].innerHTML:null):this.empty().append(value);},replaceWith:function(value){return this.after(value).remove();},eq:function(i){return this.slice(i,i+1);},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments));},map:function(callback){return this.pushStack(jQuery.map(this,function(elem,i){return callback.call(elem,i,elem);}));},andSelf:function(){return this.add(this.prevObject);},data:function(key,value){var parts=key.split(".");parts[1]=parts[1]?"."+parts[1]:"";if(value===undefined){var data=this.triggerHandler("getData"+parts[1]+"!",[parts[0]]);if(data===undefined&&this.length)data=jQuery.data(this[0],key);return data===undefined&&parts[1]?this.data(parts[0]):data;}else -return this.trigger("setData"+parts[1]+"!",[parts[0],value]).each(function(){jQuery.data(this,key,value);});},removeData:function(key){return this.each(function(){jQuery.removeData(this,key);});},domManip:function(args,table,reverse,callback){var clone=this.length>1,elems;return this.each(function(){if(!elems){elems=jQuery.clean(args,this.ownerDocument);if(reverse)elems.reverse();}var obj=this;if(table&&jQuery.nodeName(this,"table")&&jQuery.nodeName(elems[0],"tr"))obj=this.getElementsByTagName("tbody")[0]||this.appendChild(this.ownerDocument.createElement("tbody"));var scripts=jQuery([]);jQuery.each(elems,function(){var elem=clone?jQuery(this).clone(true)[0]:this;if(jQuery.nodeName(elem,"script"))scripts=scripts.add(elem);else{if(elem.nodeType==1)scripts=scripts.add(jQuery("script",elem).remove());callback.call(obj,elem);}});scripts.each(evalScript);});}};jQuery.fn.init.prototype=jQuery.fn;function evalScript(i,elem){if(elem.src)jQuery.ajax({url:elem.src,async:false,dataType:"script"});else -jQuery.globalEval(elem.text||elem.textContent||elem.innerHTML||"");if(elem.parentNode)elem.parentNode.removeChild(elem);}function now(){return+new Date;}jQuery.extend=jQuery.fn.extend=function(){var target=arguments[0]||{},i=1,length=arguments.length,deep=false,options;if(target.constructor==Boolean){deep=target;target=arguments[1]||{};i=2;}if(typeof target!="object"&&typeof target!="function")target={};if(length==i){target=this;--i;}for(;i<length;i++)if((options=arguments[i])!=null)for(var name in options){var src=target[name],copy=options[name];if(target===copy)continue;if(deep&©&&typeof copy=="object"&&!copy.nodeType)target[name]=jQuery.extend(deep,src||(copy.length!=null?[]:{}),copy);else if(copy!==undefined)target[name]=copy;}return target;};var expando="jQuery"+now(),uuid=0,windowData={},exclude=/z-?index|font-?weight|opacity|zoom|line-?height/i,defaultView=document.defaultView||{};jQuery.extend({noConflict:function(deep){window.$=_$;if(deep)window.jQuery=_jQuery;return jQuery;},isFunction:function(fn){return!!fn&&typeof fn!="string"&&!fn.nodeName&&fn.constructor!=Array&&/^[\s[]?function/.test(fn+"");},isXMLDoc:function(elem){return elem.documentElement&&!elem.body||elem.tagName&&elem.ownerDocument&&!elem.ownerDocument.body;},globalEval:function(data){data=jQuery.trim(data);if(data){var head=document.getElementsByTagName("head")[0]||document.documentElement,script=document.createElement("script");script.type="text/javascript";if(jQuery.browser.msie)script.text=data;else -script.appendChild(document.createTextNode(data));head.insertBefore(script,head.firstChild);head.removeChild(script);}},nodeName:function(elem,name){return elem.nodeName&&elem.nodeName.toUpperCase()==name.toUpperCase();},cache:{},data:function(elem,name,data){elem=elem==window?windowData:elem;var id=elem[expando];if(!id)id=elem[expando]=++uuid;if(name&&!jQuery.cache[id])jQuery.cache[id]={};if(data!==undefined)jQuery.cache[id][name]=data;return name?jQuery.cache[id][name]:id;},removeData:function(elem,name){elem=elem==window?windowData:elem;var id=elem[expando];if(name){if(jQuery.cache[id]){delete jQuery.cache[id][name];name="";for(name in jQuery.cache[id])break;if(!name)jQuery.removeData(elem);}}else{try{delete elem[expando];}catch(e){if(elem.removeAttribute)elem.removeAttribute(expando);}delete jQuery.cache[id];}},each:function(object,callback,args){var name,i=0,length=object.length;if(args){if(length==undefined){for(name in object)if(callback.apply(object[name],args)===false)break;}else -for(;i<length;)if(callback.apply(object[i++],args)===false)break;}else{if(length==undefined){for(name in object)if(callback.call(object[name],name,object[name])===false)break;}else -for(var value=object[0];i<length&&callback.call(value,i,value)!==false;value=object[++i]){}}return object;},prop:function(elem,value,type,i,name){if(jQuery.isFunction(value))value=value.call(elem,i);return value&&value.constructor==Number&&type=="curCSS"&&!exclude.test(name)?value+"px":value;},className:{add:function(elem,classNames){jQuery.each((classNames||"").split(/\s+/),function(i,className){if(elem.nodeType==1&&!jQuery.className.has(elem.className,className))elem.className+=(elem.className?" ":"")+className;});},remove:function(elem,classNames){if(elem.nodeType==1)elem.className=classNames!=undefined?jQuery.grep(elem.className.split(/\s+/),function(className){return!jQuery.className.has(classNames,className);}).join(" "):"";},has:function(elem,className){return jQuery.inArray(className,(elem.className||elem).toString().split(/\s+/))>-1;}},swap:function(elem,options,callback){var old={};for(var name in options){old[name]=elem.style[name];elem.style[name]=options[name];}callback.call(elem);for(var name in options)elem.style[name]=old[name];},css:function(elem,name,force){if(name=="width"||name=="height"){var val,props={position:"absolute",visibility:"hidden",display:"block"},which=name=="width"?["Left","Right"]:["Top","Bottom"];function getWH(){val=name=="width"?elem.offsetWidth:elem.offsetHeight;var padding=0,border=0;jQuery.each(which,function(){padding+=parseFloat(jQuery.curCSS(elem,"padding"+this,true))||0;border+=parseFloat(jQuery.curCSS(elem,"border"+this+"Width",true))||0;});val-=Math.round(padding+border);}if(jQuery(elem).is(":visible"))getWH();else -jQuery.swap(elem,props,getWH);return Math.max(0,val);}return jQuery.curCSS(elem,name,force);},curCSS:function(elem,name,force){var ret,style=elem.style;function color(elem){if(!jQuery.browser.safari)return false;var ret=defaultView.getComputedStyle(elem,null);return!ret||ret.getPropertyValue("color")=="";}if(name=="opacity"&&jQuery.browser.msie){ret=jQuery.attr(style,"opacity");return ret==""?"1":ret;}if(jQuery.browser.opera&&name=="display"){var save=style.outline;style.outline="0 solid black";style.outline=save;}if(name.match(/float/i))name=styleFloat;if(!force&&style&&style[name])ret=style[name];else if(defaultView.getComputedStyle){if(name.match(/float/i))name="float";name=name.replace(/([A-Z])/g,"-$1").toLowerCase();var computedStyle=defaultView.getComputedStyle(elem,null);if(computedStyle&&!color(elem))ret=computedStyle.getPropertyValue(name);else{var swap=[],stack=[],a=elem,i=0;for(;a&&color(a);a=a.parentNode)stack.unshift(a);for(;i<stack.length;i++)if(color(stack[i])){swap[i]=stack[i].style.display;stack[i].style.display="block";}ret=name=="display"&&swap[stack.length-1]!=null?"none":(computedStyle&&computedStyle.getPropertyValue(name))||"";for(i=0;i<swap.length;i++)if(swap[i]!=null)stack[i].style.display=swap[i];}if(name=="opacity"&&ret=="")ret="1";}else if(elem.currentStyle){var camelCase=name.replace(/\-(\w)/g,function(all,letter){return letter.toUpperCase();});ret=elem.currentStyle[name]||elem.currentStyle[camelCase];if(!/^\d+(px)?$/i.test(ret)&&/^\d/.test(ret)){var left=style.left,rsLeft=elem.runtimeStyle.left;elem.runtimeStyle.left=elem.currentStyle.left;style.left=ret||0;ret=style.pixelLeft+"px";style.left=left;elem.runtimeStyle.left=rsLeft;}}return ret;},clean:function(elems,context){var ret=[];context=context||document;if(typeof context.createElement=='undefined')context=context.ownerDocument||context[0]&&context[0].ownerDocument||document;jQuery.each(elems,function(i,elem){if(!elem)return;if(elem.constructor==Number)elem+='';if(typeof elem=="string"){elem=elem.replace(/(<(\w+)[^>]*?)\/>/g,function(all,front,tag){return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?all:front+"></"+tag+">";});var tags=jQuery.trim(elem).toLowerCase(),div=context.createElement("div");var wrap=!tags.indexOf("<opt")&&[1,"<select multiple='multiple'>","</select>"]||!tags.indexOf("<leg")&&[1,"<fieldset>","</fieldset>"]||tags.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"<table>","</table>"]||!tags.indexOf("<tr")&&[2,"<table><tbody>","</tbody></table>"]||(!tags.indexOf("<td")||!tags.indexOf("<th"))&&[3,"<table><tbody><tr>","</tr></tbody></table>"]||!tags.indexOf("<col")&&[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"]||jQuery.browser.msie&&[1,"div<div>","</div>"]||[0,"",""];div.innerHTML=wrap[1]+elem+wrap[2];while(wrap[0]--)div=div.lastChild;if(jQuery.browser.msie){var tbody=!tags.indexOf("<table")&&tags.indexOf("<tbody")<0?div.firstChild&&div.firstChild.childNodes:wrap[1]=="<table>"&&tags.indexOf("<tbody")<0?div.childNodes:[];for(var j=tbody.length-1;j>=0;--j)if(jQuery.nodeName(tbody[j],"tbody")&&!tbody[j].childNodes.length)tbody[j].parentNode.removeChild(tbody[j]);if(/^\s/.test(elem))div.insertBefore(context.createTextNode(elem.match(/^\s*/)[0]),div.firstChild);}elem=jQuery.makeArray(div.childNodes);}if(elem.length===0&&(!jQuery.nodeName(elem,"form")&&!jQuery.nodeName(elem,"select")))return;if(elem[0]==undefined||jQuery.nodeName(elem,"form")||elem.options)ret.push(elem);else -ret=jQuery.merge(ret,elem);});return ret;},attr:function(elem,name,value){if(!elem||elem.nodeType==3||elem.nodeType==8)return undefined;var notxml=!jQuery.isXMLDoc(elem),set=value!==undefined,msie=jQuery.browser.msie;name=notxml&&jQuery.props[name]||name;if(elem.tagName){var special=/href|src|style/.test(name);if(name=="selected"&&jQuery.browser.safari)elem.parentNode.selectedIndex;if(name in elem&¬xml&&!special){if(set){if(name=="type"&&jQuery.nodeName(elem,"input")&&elem.parentNode)throw"type property can't be changed";elem[name]=value;}if(jQuery.nodeName(elem,"form")&&elem.getAttributeNode(name))return elem.getAttributeNode(name).nodeValue;return elem[name];}if(msie&¬xml&&name=="style")return jQuery.attr(elem.style,"cssText",value);if(set)elem.setAttribute(name,""+value);var attr=msie&¬xml&&special?elem.getAttribute(name,2):elem.getAttribute(name);return attr===null?undefined:attr;}if(msie&&name=="opacity"){if(set){elem.zoom=1;elem.filter=(elem.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(value)+''=="NaN"?"":"alpha(opacity="+value*100+")");}return elem.filter&&elem.filter.indexOf("opacity=")>=0?(parseFloat(elem.filter.match(/opacity=([^)]*)/)[1])/100)+'':"";}name=name.replace(/-([a-z])/ig,function(all,letter){return letter.toUpperCase();});if(set)elem[name]=value;return elem[name];},trim:function(text){return(text||"").replace(/^\s+|\s+$/g,"");},makeArray:function(array){var ret=[];if(array!=null){var i=array.length;if(i==null||array.split||array.setInterval||array.call)ret[0]=array;else -while(i)ret[--i]=array[i];}return ret;},inArray:function(elem,array){for(var i=0,length=array.length;i<length;i++)if(array[i]===elem)return i;return-1;},merge:function(first,second){var i=0,elem,pos=first.length;if(jQuery.browser.msie){while(elem=second[i++])if(elem.nodeType!=8)first[pos++]=elem;}else -while(elem=second[i++])first[pos++]=elem;return first;},unique:function(array){var ret=[],done={};try{for(var i=0,length=array.length;i<length;i++){var id=jQuery.data(array[i]);if(!done[id]){done[id]=true;ret.push(array[i]);}}}catch(e){ret=array;}return ret;},grep:function(elems,callback,inv){var ret=[];for(var i=0,length=elems.length;i<length;i++)if(!inv!=!callback(elems[i],i))ret.push(elems[i]);return ret;},map:function(elems,callback){var ret=[];for(var i=0,length=elems.length;i<length;i++){var value=callback(elems[i],i);if(value!=null)ret[ret.length]=value;}return ret.concat.apply([],ret);}});var userAgent=navigator.userAgent.toLowerCase();jQuery.browser={version:(userAgent.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)||[])[1],safari:/webkit/.test(userAgent),opera:/opera/.test(userAgent),msie:/msie/.test(userAgent)&&!/opera/.test(userAgent),mozilla:/mozilla/.test(userAgent)&&!/(compatible|webkit)/.test(userAgent)};var styleFloat=jQuery.browser.msie?"styleFloat":"cssFloat";jQuery.extend({boxModel:!jQuery.browser.msie||document.compatMode=="CSS1Compat",props:{"for":"htmlFor","class":"className","float":styleFloat,cssFloat:styleFloat,styleFloat:styleFloat,readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing"}});jQuery.each({parent:function(elem){return elem.parentNode;},parents:function(elem){return jQuery.dir(elem,"parentNode");},next:function(elem){return jQuery.nth(elem,2,"nextSibling");},prev:function(elem){return jQuery.nth(elem,2,"previousSibling");},nextAll:function(elem){return jQuery.dir(elem,"nextSibling");},prevAll:function(elem){return jQuery.dir(elem,"previousSibling");},siblings:function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);},children:function(elem){return jQuery.sibling(elem.firstChild);},contents:function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);}},function(name,fn){jQuery.fn[name]=function(selector){var ret=jQuery.map(this,fn);if(selector&&typeof selector=="string")ret=jQuery.multiFilter(selector,ret);return this.pushStack(jQuery.unique(ret));};});jQuery.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(name,original){jQuery.fn[name]=function(){var args=arguments;return this.each(function(){for(var i=0,length=args.length;i<length;i++)jQuery(args[i])[original](this);});};});jQuery.each({removeAttr:function(name){jQuery.attr(this,name,"");if(this.nodeType==1)this.removeAttribute(name);},addClass:function(classNames){jQuery.className.add(this,classNames);},removeClass:function(classNames){jQuery.className.remove(this,classNames);},toggleClass:function(classNames){jQuery.className[jQuery.className.has(this,classNames)?"remove":"add"](this,classNames);},remove:function(selector){if(!selector||jQuery.filter(selector,[this]).r.length){jQuery("*",this).add(this).each(function(){jQuery.event.remove(this);jQuery.removeData(this);});if(this.parentNode)this.parentNode.removeChild(this);}},empty:function(){jQuery(">*",this).remove();while(this.firstChild)this.removeChild(this.firstChild);}},function(name,fn){jQuery.fn[name]=function(){return this.each(fn,arguments);};});jQuery.each(["Height","Width"],function(i,name){var type=name.toLowerCase();jQuery.fn[type]=function(size){return this[0]==window?jQuery.browser.opera&&document.body["client"+name]||jQuery.browser.safari&&window["inner"+name]||document.compatMode=="CSS1Compat"&&document.documentElement["client"+name]||document.body["client"+name]:this[0]==document?Math.max(Math.max(document.body["scroll"+name],document.documentElement["scroll"+name]),Math.max(document.body["offset"+name],document.documentElement["offset"+name])):size==undefined?(this.length?jQuery.css(this[0],type):null):this.css(type,size.constructor==String?size:size+"px");};});function num(elem,prop){return elem[0]&&parseInt(jQuery.curCSS(elem[0],prop,true),10)||0;}var chars=jQuery.browser.safari&&parseInt(jQuery.browser.version)<417?"(?:[\\w*_-]|\\\\.)":"(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",quickChild=new RegExp("^>\\s*("+chars+"+)"),quickID=new RegExp("^("+chars+"+)(#)("+chars+"+)"),quickClass=new RegExp("^([#.]?)("+chars+"*)");jQuery.extend({expr:{"":function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);},"#":function(a,i,m){return a.getAttribute("id")==m[2];},":":{lt:function(a,i,m){return i<m[3]-0;},gt:function(a,i,m){return i>m[3]-0;},nth:function(a,i,m){return m[3]-0==i;},eq:function(a,i,m){return m[3]-0==i;},first:function(a,i){return i==0;},last:function(a,i,m,r){return i==r.length-1;},even:function(a,i){return i%2==0;},odd:function(a,i){return i%2;},"first-child":function(a){return a.parentNode.getElementsByTagName("*")[0]==a;},"last-child":function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;},"only-child":function(a){return!jQuery.nth(a.parentNode.lastChild,2,"previousSibling");},parent:function(a){return a.firstChild;},empty:function(a){return!a.firstChild;},contains:function(a,i,m){return(a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;},visible:function(a){return"hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";},hidden:function(a){return"hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";},enabled:function(a){return!a.disabled;},disabled:function(a){return a.disabled;},checked:function(a){return a.checked;},selected:function(a){return a.selected||jQuery.attr(a,"selected");},text:function(a){return"text"==a.type;},radio:function(a){return"radio"==a.type;},checkbox:function(a){return"checkbox"==a.type;},file:function(a){return"file"==a.type;},password:function(a){return"password"==a.type;},submit:function(a){return"submit"==a.type;},image:function(a){return"image"==a.type;},reset:function(a){return"reset"==a.type;},button:function(a){return"button"==a.type||jQuery.nodeName(a,"button");},input:function(a){return/input|select|textarea|button/i.test(a.nodeName);},has:function(a,i,m){return jQuery.find(m[3],a).length;},header:function(a){return/h\d/i.test(a.nodeName);},animated:function(a){return jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length;}}},parse:[/^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,new RegExp("^([:.#]*)("+chars+"+)")],multiFilter:function(expr,elems,not){var old,cur=[];while(expr&&expr!=old){old=expr;var f=jQuery.filter(expr,elems,not);expr=f.t.replace(/^\s*,\s*/,"");cur=not?elems=f.r:jQuery.merge(cur,f.r);}return cur;},find:function(t,context){if(typeof t!="string")return[t];if(context&&context.nodeType!=1&&context.nodeType!=9)return[];context=context||document;var ret=[context],done=[],last,nodeName;while(t&&last!=t){var r=[];last=t;t=jQuery.trim(t);var foundToken=false,re=quickChild,m=re.exec(t);if(m){nodeName=m[1].toUpperCase();for(var i=0;ret[i];i++)for(var c=ret[i].firstChild;c;c=c.nextSibling)if(c.nodeType==1&&(nodeName=="*"||c.nodeName.toUpperCase()==nodeName))r.push(c);ret=r;t=t.replace(re,"");if(t.indexOf(" ")==0)continue;foundToken=true;}else{re=/^([>+~])\s*(\w*)/i;if((m=re.exec(t))!=null){r=[];var merge={};nodeName=m[2].toUpperCase();m=m[1];for(var j=0,rl=ret.length;j<rl;j++){var n=m=="~"||m=="+"?ret[j].nextSibling:ret[j].firstChild;for(;n;n=n.nextSibling)if(n.nodeType==1){var id=jQuery.data(n);if(m=="~"&&merge[id])break;if(!nodeName||n.nodeName.toUpperCase()==nodeName){if(m=="~")merge[id]=true;r.push(n);}if(m=="+")break;}}ret=r;t=jQuery.trim(t.replace(re,""));foundToken=true;}}if(t&&!foundToken){if(!t.indexOf(",")){if(context==ret[0])ret.shift();done=jQuery.merge(done,ret);r=ret=[context];t=" "+t.substr(1,t.length);}else{var re2=quickID;var m=re2.exec(t);if(m){m=[0,m[2],m[3],m[1]];}else{re2=quickClass;m=re2.exec(t);}m[2]=m[2].replace(/\\/g,"");var elem=ret[ret.length-1];if(m[1]=="#"&&elem&&elem.getElementById&&!jQuery.isXMLDoc(elem)){var oid=elem.getElementById(m[2]);if((jQuery.browser.msie||jQuery.browser.opera)&&oid&&typeof oid.id=="string"&&oid.id!=m[2])oid=jQuery('[@id="'+m[2]+'"]',elem)[0];ret=r=oid&&(!m[3]||jQuery.nodeName(oid,m[3]))?[oid]:[];}else{for(var i=0;ret[i];i++){var tag=m[1]=="#"&&m[3]?m[3]:m[1]!=""||m[0]==""?"*":m[2];if(tag=="*"&&ret[i].nodeName.toLowerCase()=="object")tag="param";r=jQuery.merge(r,ret[i].getElementsByTagName(tag));}if(m[1]==".")r=jQuery.classFilter(r,m[2]);if(m[1]=="#"){var tmp=[];for(var i=0;r[i];i++)if(r[i].getAttribute("id")==m[2]){tmp=[r[i]];break;}r=tmp;}ret=r;}t=t.replace(re2,"");}}if(t){var val=jQuery.filter(t,r);ret=r=val.r;t=jQuery.trim(val.t);}}if(t)ret=[];if(ret&&context==ret[0])ret.shift();done=jQuery.merge(done,ret);return done;},classFilter:function(r,m,not){m=" "+m+" ";var tmp=[];for(var i=0;r[i];i++){var pass=(" "+r[i].className+" ").indexOf(m)>=0;if(!not&&pass||not&&!pass)tmp.push(r[i]);}return tmp;},filter:function(t,r,not){var last;while(t&&t!=last){last=t;var p=jQuery.parse,m;for(var i=0;p[i];i++){m=p[i].exec(t);if(m){t=t.substring(m[0].length);m[2]=m[2].replace(/\\/g,"");break;}}if(!m)break;if(m[1]==":"&&m[2]=="not")r=isSimple.test(m[3])?jQuery.filter(m[3],r,true).r:jQuery(r).not(m[3]);else if(m[1]==".")r=jQuery.classFilter(r,m[2],not);else if(m[1]=="["){var tmp=[],type=m[3];for(var i=0,rl=r.length;i<rl;i++){var a=r[i],z=a[jQuery.props[m[2]]||m[2]];if(z==null||/href|src|selected/.test(m[2]))z=jQuery.attr(a,m[2])||'';if((type==""&&!!z||type=="="&&z==m[5]||type=="!="&&z!=m[5]||type=="^="&&z&&!z.indexOf(m[5])||type=="$="&&z.substr(z.length-m[5].length)==m[5]||(type=="*="||type=="~=")&&z.indexOf(m[5])>=0)^not)tmp.push(a);}r=tmp;}else if(m[1]==":"&&m[2]=="nth-child"){var merge={},tmp=[],test=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(m[3]=="even"&&"2n"||m[3]=="odd"&&"2n+1"||!/\D/.test(m[3])&&"0n+"+m[3]||m[3]),first=(test[1]+(test[2]||1))-0,last=test[3]-0;for(var i=0,rl=r.length;i<rl;i++){var node=r[i],parentNode=node.parentNode,id=jQuery.data(parentNode);if(!merge[id]){var c=1;for(var n=parentNode.firstChild;n;n=n.nextSibling)if(n.nodeType==1)n.nodeIndex=c++;merge[id]=true;}var add=false;if(first==0){if(node.nodeIndex==last)add=true;}else if((node.nodeIndex-last)%first==0&&(node.nodeIndex-last)/first>=0)add=true;if(add^not)tmp.push(node);}r=tmp;}else{var fn=jQuery.expr[m[1]];if(typeof fn=="object")fn=fn[m[2]];if(typeof fn=="string")fn=eval("false||function(a,i){return "+fn+";}");r=jQuery.grep(r,function(elem,i){return fn(elem,i,m,r);},not);}}return{r:r,t:t};},dir:function(elem,dir){var matched=[],cur=elem[dir];while(cur&&cur!=document){if(cur.nodeType==1)matched.push(cur);cur=cur[dir];}return matched;},nth:function(cur,result,dir,elem){result=result||1;var num=0;for(;cur;cur=cur[dir])if(cur.nodeType==1&&++num==result)break;return cur;},sibling:function(n,elem){var r=[];for(;n;n=n.nextSibling){if(n.nodeType==1&&n!=elem)r.push(n);}return r;}});jQuery.event={add:function(elem,types,handler,data){if(elem.nodeType==3||elem.nodeType==8)return;if(jQuery.browser.msie&&elem.setInterval)elem=window;if(!handler.guid)handler.guid=this.guid++;if(data!=undefined){var fn=handler;handler=this.proxy(fn,function(){return fn.apply(this,arguments);});handler.data=data;}var events=jQuery.data(elem,"events")||jQuery.data(elem,"events",{}),handle=jQuery.data(elem,"handle")||jQuery.data(elem,"handle",function(){if(typeof jQuery!="undefined"&&!jQuery.event.triggered)return jQuery.event.handle.apply(arguments.callee.elem,arguments);});handle.elem=elem;jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];handler.type=parts[1];var handlers=events[type];if(!handlers){handlers=events[type]={};if(!jQuery.event.special[type]||jQuery.event.special[type].setup.call(elem)===false){if(elem.addEventListener)elem.addEventListener(type,handle,false);else if(elem.attachEvent)elem.attachEvent("on"+type,handle);}}handlers[handler.guid]=handler;jQuery.event.global[type]=true;});elem=null;},guid:1,global:{},remove:function(elem,types,handler){if(elem.nodeType==3||elem.nodeType==8)return;var events=jQuery.data(elem,"events"),ret,index;if(events){if(types==undefined||(typeof types=="string"&&types.charAt(0)=="."))for(var type in events)this.remove(elem,type+(types||""));else{if(types.type){handler=types.handler;types=types.type;}jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];if(events[type]){if(handler)delete events[type][handler.guid];else -for(handler in events[type])if(!parts[1]||events[type][handler].type==parts[1])delete events[type][handler];for(ret in events[type])break;if(!ret){if(!jQuery.event.special[type]||jQuery.event.special[type].teardown.call(elem)===false){if(elem.removeEventListener)elem.removeEventListener(type,jQuery.data(elem,"handle"),false);else if(elem.detachEvent)elem.detachEvent("on"+type,jQuery.data(elem,"handle"));}ret=null;delete events[type];}}});}for(ret in events)break;if(!ret){var handle=jQuery.data(elem,"handle");if(handle)handle.elem=null;jQuery.removeData(elem,"events");jQuery.removeData(elem,"handle");}}},trigger:function(type,data,elem,donative,extra){data=jQuery.makeArray(data);if(type.indexOf("!")>=0){type=type.slice(0,-1);var exclusive=true;}if(!elem){if(this.global[type])jQuery("*").add([window,document]).trigger(type,data);}else{if(elem.nodeType==3||elem.nodeType==8)return undefined;var val,ret,fn=jQuery.isFunction(elem[type]||null),event=!data[0]||!data[0].preventDefault;if(event){data.unshift({type:type,target:elem,preventDefault:function(){},stopPropagation:function(){},timeStamp:now()});data[0][expando]=true;}data[0].type=type;if(exclusive)data[0].exclusive=true;var handle=jQuery.data(elem,"handle");if(handle)val=handle.apply(elem,data);if((!fn||(jQuery.nodeName(elem,'a')&&type=="click"))&&elem["on"+type]&&elem["on"+type].apply(elem,data)===false)val=false;if(event)data.shift();if(extra&&jQuery.isFunction(extra)){ret=extra.apply(elem,val==null?data:data.concat(val));if(ret!==undefined)val=ret;}if(fn&&donative!==false&&val!==false&&!(jQuery.nodeName(elem,'a')&&type=="click")){this.triggered=true;try{elem[type]();}catch(e){}}this.triggered=false;}return val;},handle:function(event){var val,ret,namespace,all,handlers;event=arguments[0]=jQuery.event.fix(event||window.event);namespace=event.type.split(".");event.type=namespace[0];namespace=namespace[1];all=!namespace&&!event.exclusive;handlers=(jQuery.data(this,"events")||{})[event.type];for(var j in handlers){var handler=handlers[j];if(all||handler.type==namespace){event.handler=handler;event.data=handler.data;ret=handler.apply(this,arguments);if(val!==false)val=ret;if(ret===false){event.preventDefault();event.stopPropagation();}}}return val;},fix:function(event){if(event[expando]==true)return event;var originalEvent=event;event={originalEvent:originalEvent};var props="altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target timeStamp toElement type view wheelDelta which".split(" ");for(var i=props.length;i;i--)event[props[i]]=originalEvent[props[i]];event[expando]=true;event.preventDefault=function(){if(originalEvent.preventDefault)originalEvent.preventDefault();originalEvent.returnValue=false;};event.stopPropagation=function(){if(originalEvent.stopPropagation)originalEvent.stopPropagation();originalEvent.cancelBubble=true;};event.timeStamp=event.timeStamp||now();if(!event.target)event.target=event.srcElement||document;if(event.target.nodeType==3)event.target=event.target.parentNode;if(!event.relatedTarget&&event.fromElement)event.relatedTarget=event.fromElement==event.target?event.toElement:event.fromElement;if(event.pageX==null&&event.clientX!=null){var doc=document.documentElement,body=document.body;event.pageX=event.clientX+(doc&&doc.scrollLeft||body&&body.scrollLeft||0)-(doc.clientLeft||0);event.pageY=event.clientY+(doc&&doc.scrollTop||body&&body.scrollTop||0)-(doc.clientTop||0);}if(!event.which&&((event.charCode||event.charCode===0)?event.charCode:event.keyCode))event.which=event.charCode||event.keyCode;if(!event.metaKey&&event.ctrlKey)event.metaKey=event.ctrlKey;if(!event.which&&event.button)event.which=(event.button&1?1:(event.button&2?3:(event.button&4?2:0)));return event;},proxy:function(fn,proxy){proxy.guid=fn.guid=fn.guid||proxy.guid||this.guid++;return proxy;},special:{ready:{setup:function(){bindReady();return;},teardown:function(){return;}},mouseenter:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseover",jQuery.event.special.mouseenter.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseover",jQuery.event.special.mouseenter.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseenter";return jQuery.event.handle.apply(this,arguments);}},mouseleave:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseout",jQuery.event.special.mouseleave.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseout",jQuery.event.special.mouseleave.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseleave";return jQuery.event.handle.apply(this,arguments);}}}};jQuery.fn.extend({bind:function(type,data,fn){return type=="unload"?this.one(type,data,fn):this.each(function(){jQuery.event.add(this,type,fn||data,fn&&data);});},one:function(type,data,fn){var one=jQuery.event.proxy(fn||data,function(event){jQuery(this).unbind(event,one);return(fn||data).apply(this,arguments);});return this.each(function(){jQuery.event.add(this,type,one,fn&&data);});},unbind:function(type,fn){return this.each(function(){jQuery.event.remove(this,type,fn);});},trigger:function(type,data,fn){return this.each(function(){jQuery.event.trigger(type,data,this,true,fn);});},triggerHandler:function(type,data,fn){return this[0]&&jQuery.event.trigger(type,data,this[0],false,fn);},toggle:function(fn){var args=arguments,i=1;while(i<args.length)jQuery.event.proxy(fn,args[i++]);return this.click(jQuery.event.proxy(fn,function(event){this.lastToggle=(this.lastToggle||0)%i;event.preventDefault();return args[this.lastToggle++].apply(this,arguments)||false;}));},hover:function(fnOver,fnOut){return this.bind('mouseenter',fnOver).bind('mouseleave',fnOut);},ready:function(fn){bindReady();if(jQuery.isReady)fn.call(document,jQuery);else -jQuery.readyList.push(function(){return fn.call(this,jQuery);});return this;}});jQuery.extend({isReady:false,readyList:[],ready:function(){if(!jQuery.isReady){jQuery.isReady=true;if(jQuery.readyList){jQuery.each(jQuery.readyList,function(){this.call(document);});jQuery.readyList=null;}jQuery(document).triggerHandler("ready");}}});var readyBound=false;function bindReady(){if(readyBound)return;readyBound=true;if(document.addEventListener&&!jQuery.browser.opera)document.addEventListener("DOMContentLoaded",jQuery.ready,false);if(jQuery.browser.msie&&window==top)(function(){if(jQuery.isReady)return;try{document.documentElement.doScroll("left");}catch(error){setTimeout(arguments.callee,0);return;}jQuery.ready();})();if(jQuery.browser.opera)document.addEventListener("DOMContentLoaded",function(){if(jQuery.isReady)return;for(var i=0;i<document.styleSheets.length;i++)if(document.styleSheets[i].disabled){setTimeout(arguments.callee,0);return;}jQuery.ready();},false);if(jQuery.browser.safari){var numStyles;(function(){if(jQuery.isReady)return;if(document.readyState!="loaded"&&document.readyState!="complete"){setTimeout(arguments.callee,0);return;}if(numStyles===undefined)numStyles=jQuery("style, link[rel=stylesheet]").length;if(document.styleSheets.length!=numStyles){setTimeout(arguments.callee,0);return;}jQuery.ready();})();}jQuery.event.add(window,"load",jQuery.ready);}jQuery.each(("blur,focus,load,resize,scroll,unload,click,dblclick,"+"mousedown,mouseup,mousemove,mouseover,mouseout,change,select,"+"submit,keydown,keypress,keyup,error").split(","),function(i,name){jQuery.fn[name]=function(fn){return fn?this.bind(name,fn):this.trigger(name);};});var withinElement=function(event,elem){var parent=event.relatedTarget;while(parent&&parent!=elem)try{parent=parent.parentNode;}catch(error){parent=elem;}return parent==elem;};jQuery(window).bind("unload",function(){jQuery("*").add(document).unbind();});jQuery.fn.extend({_load:jQuery.fn.load,load:function(url,params,callback){if(typeof url!='string')return this._load(url);var off=url.indexOf(" ");if(off>=0){var selector=url.slice(off,url.length);url=url.slice(0,off);}callback=callback||function(){};var type="GET";if(params)if(jQuery.isFunction(params)){callback=params;params=null;}else{params=jQuery.param(params);type="POST";}var self=this;jQuery.ajax({url:url,type:type,dataType:"html",data:params,complete:function(res,status){if(status=="success"||status=="notmodified")self.html(selector?jQuery("<div/>").append(res.responseText.replace(/<script(.|\s)*?\/script>/g,"")).find(selector):res.responseText);self.each(callback,[res.responseText,status,res]);}});return this;},serialize:function(){return jQuery.param(this.serializeArray());},serializeArray:function(){return this.map(function(){return jQuery.nodeName(this,"form")?jQuery.makeArray(this.elements):this;}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type));}).map(function(i,elem){var val=jQuery(this).val();return val==null?null:val.constructor==Array?jQuery.map(val,function(val,i){return{name:elem.name,value:val};}):{name:elem.name,value:val};}).get();}});jQuery.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(i,o){jQuery.fn[o]=function(f){return this.bind(o,f);};});var jsc=now();jQuery.extend({get:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data=null;}return jQuery.ajax({type:"GET",url:url,data:data,success:callback,dataType:type});},getScript:function(url,callback){return jQuery.get(url,null,callback,"script");},getJSON:function(url,data,callback){return jQuery.get(url,data,callback,"json");},post:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data={};}return jQuery.ajax({type:"POST",url:url,data:data,success:callback,dataType:type});},ajaxSetup:function(settings){jQuery.extend(jQuery.ajaxSettings,settings);},ajaxSettings:{url:location.href,global:true,type:"GET",timeout:0,contentType:"application/x-www-form-urlencoded",processData:true,async:true,data:null,username:null,password:null,accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(s){s=jQuery.extend(true,s,jQuery.extend(true,{},jQuery.ajaxSettings,s));var jsonp,jsre=/=\?(&|$)/g,status,data,type=s.type.toUpperCase();if(s.data&&s.processData&&typeof s.data!="string")s.data=jQuery.param(s.data);if(s.dataType=="jsonp"){if(type=="GET"){if(!s.url.match(jsre))s.url+=(s.url.match(/\?/)?"&":"?")+(s.jsonp||"callback")+"=?";}else if(!s.data||!s.data.match(jsre))s.data=(s.data?s.data+"&":"")+(s.jsonp||"callback")+"=?";s.dataType="json";}if(s.dataType=="json"&&(s.data&&s.data.match(jsre)||s.url.match(jsre))){jsonp="jsonp"+jsc++;if(s.data)s.data=(s.data+"").replace(jsre,"="+jsonp+"$1");s.url=s.url.replace(jsre,"="+jsonp+"$1");s.dataType="script";window[jsonp]=function(tmp){data=tmp;success();complete();window[jsonp]=undefined;try{delete window[jsonp];}catch(e){}if(head)head.removeChild(script);};}if(s.dataType=="script"&&s.cache==null)s.cache=false;if(s.cache===false&&type=="GET"){var ts=now();var ret=s.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+ts+"$2");s.url=ret+((ret==s.url)?(s.url.match(/\?/)?"&":"?")+"_="+ts:"");}if(s.data&&type=="GET"){s.url+=(s.url.match(/\?/)?"&":"?")+s.data;s.data=null;}if(s.global&&!jQuery.active++)jQuery.event.trigger("ajaxStart");var remote=/^(?:\w+:)?\/\/([^\/?#]+)/;if(s.dataType=="script"&&type=="GET"&&remote.test(s.url)&&remote.exec(s.url)[1]!=location.host){var head=document.getElementsByTagName("head")[0];var script=document.createElement("script");script.src=s.url;if(s.scriptCharset)script.charset=s.scriptCharset;if(!jsonp){var done=false;script.onload=script.onreadystatechange=function(){if(!done&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){done=true;success();complete();head.removeChild(script);}};}head.appendChild(script);return undefined;}var requestDone=false;var xhr=window.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest();if(s.username)xhr.open(type,s.url,s.async,s.username,s.password);else -xhr.open(type,s.url,s.async);try{if(s.data)xhr.setRequestHeader("Content-Type",s.contentType);if(s.ifModified)xhr.setRequestHeader("If-Modified-Since",jQuery.lastModified[s.url]||"Thu, 01 Jan 1970 00:00:00 GMT");xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Accept",s.dataType&&s.accepts[s.dataType]?s.accepts[s.dataType]+", */*":s.accepts._default);}catch(e){}if(s.beforeSend&&s.beforeSend(xhr,s)===false){s.global&&jQuery.active--;xhr.abort();return false;}if(s.global)jQuery.event.trigger("ajaxSend",[xhr,s]);var onreadystatechange=function(isTimeout){if(!requestDone&&xhr&&(xhr.readyState==4||isTimeout=="timeout")){requestDone=true;if(ival){clearInterval(ival);ival=null;}status=isTimeout=="timeout"&&"timeout"||!jQuery.httpSuccess(xhr)&&"error"||s.ifModified&&jQuery.httpNotModified(xhr,s.url)&&"notmodified"||"success";if(status=="success"){try{data=jQuery.httpData(xhr,s.dataType,s.dataFilter);}catch(e){status="parsererror";}}if(status=="success"){var modRes;try{modRes=xhr.getResponseHeader("Last-Modified");}catch(e){}if(s.ifModified&&modRes)jQuery.lastModified[s.url]=modRes;if(!jsonp)success();}else -jQuery.handleError(s,xhr,status);complete();if(s.async)xhr=null;}};if(s.async){var ival=setInterval(onreadystatechange,13);if(s.timeout>0)setTimeout(function(){if(xhr){xhr.abort();if(!requestDone)onreadystatechange("timeout");}},s.timeout);}try{xhr.send(s.data);}catch(e){jQuery.handleError(s,xhr,null,e);}if(!s.async)onreadystatechange();function success(){if(s.success)s.success(data,status);if(s.global)jQuery.event.trigger("ajaxSuccess",[xhr,s]);}function complete(){if(s.complete)s.complete(xhr,status);if(s.global)jQuery.event.trigger("ajaxComplete",[xhr,s]);if(s.global&&!--jQuery.active)jQuery.event.trigger("ajaxStop");}return xhr;},handleError:function(s,xhr,status,e){if(s.error)s.error(xhr,status,e);if(s.global)jQuery.event.trigger("ajaxError",[xhr,s,e]);},active:0,httpSuccess:function(xhr){try{return!xhr.status&&location.protocol=="file:"||(xhr.status>=200&&xhr.status<300)||xhr.status==304||xhr.status==1223||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpNotModified:function(xhr,url){try{var xhrRes=xhr.getResponseHeader("Last-Modified");return xhr.status==304||xhrRes==jQuery.lastModified[url]||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpData:function(xhr,type,filter){var ct=xhr.getResponseHeader("content-type"),xml=type=="xml"||!type&&ct&&ct.indexOf("xml")>=0,data=xml?xhr.responseXML:xhr.responseText;if(xml&&data.documentElement.tagName=="parsererror")throw"parsererror";if(filter)data=filter(data,type);if(type=="script")jQuery.globalEval(data);if(type=="json")data=eval("("+data+")");return data;},param:function(a){var s=[];if(a.constructor==Array||a.jquery)jQuery.each(a,function(){s.push(encodeURIComponent(this.name)+"="+encodeURIComponent(this.value));});else -for(var j in a)if(a[j]&&a[j].constructor==Array)jQuery.each(a[j],function(){s.push(encodeURIComponent(j)+"="+encodeURIComponent(this));});else -s.push(encodeURIComponent(j)+"="+encodeURIComponent(jQuery.isFunction(a[j])?a[j]():a[j]));return s.join("&").replace(/%20/g,"+");}});jQuery.fn.extend({show:function(speed,callback){return speed?this.animate({height:"show",width:"show",opacity:"show"},speed,callback):this.filter(":hidden").each(function(){this.style.display=this.oldblock||"";if(jQuery.css(this,"display")=="none"){var elem=jQuery("<"+this.tagName+" />").appendTo("body");this.style.display=elem.css("display");if(this.style.display=="none")this.style.display="block";elem.remove();}}).end();},hide:function(speed,callback){return speed?this.animate({height:"hide",width:"hide",opacity:"hide"},speed,callback):this.filter(":visible").each(function(){this.oldblock=this.oldblock||jQuery.css(this,"display");this.style.display="none";}).end();},_toggle:jQuery.fn.toggle,toggle:function(fn,fn2){return jQuery.isFunction(fn)&&jQuery.isFunction(fn2)?this._toggle.apply(this,arguments):fn?this.animate({height:"toggle",width:"toggle",opacity:"toggle"},fn,fn2):this.each(function(){jQuery(this)[jQuery(this).is(":hidden")?"show":"hide"]();});},slideDown:function(speed,callback){return this.animate({height:"show"},speed,callback);},slideUp:function(speed,callback){return this.animate({height:"hide"},speed,callback);},slideToggle:function(speed,callback){return this.animate({height:"toggle"},speed,callback);},fadeIn:function(speed,callback){return this.animate({opacity:"show"},speed,callback);},fadeOut:function(speed,callback){return this.animate({opacity:"hide"},speed,callback);},fadeTo:function(speed,to,callback){return this.animate({opacity:to},speed,callback);},animate:function(prop,speed,easing,callback){var optall=jQuery.speed(speed,easing,callback);return this[optall.queue===false?"each":"queue"](function(){if(this.nodeType!=1)return false;var opt=jQuery.extend({},optall),p,hidden=jQuery(this).is(":hidden"),self=this;for(p in prop){if(prop[p]=="hide"&&hidden||prop[p]=="show"&&!hidden)return opt.complete.call(this);if(p=="height"||p=="width"){opt.display=jQuery.css(this,"display");opt.overflow=this.style.overflow;}}if(opt.overflow!=null)this.style.overflow="hidden";opt.curAnim=jQuery.extend({},prop);jQuery.each(prop,function(name,val){var e=new jQuery.fx(self,opt,name);if(/toggle|show|hide/.test(val))e[val=="toggle"?hidden?"show":"hide":val](prop);else{var parts=val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),start=e.cur(true)||0;if(parts){var end=parseFloat(parts[2]),unit=parts[3]||"px";if(unit!="px"){self.style[name]=(end||1)+unit;start=((end||1)/e.cur(true))*start;self.style[name]=start+unit;}if(parts[1])end=((parts[1]=="-="?-1:1)*end)+start;e.custom(start,end,unit);}else -e.custom(start,val,"");}});return true;});},queue:function(type,fn){if(jQuery.isFunction(type)||(type&&type.constructor==Array)){fn=type;type="fx";}if(!type||(typeof type=="string"&&!fn))return queue(this[0],type);return this.each(function(){if(fn.constructor==Array)queue(this,type,fn);else{queue(this,type).push(fn);if(queue(this,type).length==1)fn.call(this);}});},stop:function(clearQueue,gotoEnd){var timers=jQuery.timers;if(clearQueue)this.queue([]);this.each(function(){for(var i=timers.length-1;i>=0;i--)if(timers[i].elem==this){if(gotoEnd)timers[i](true);timers.splice(i,1);}});if(!gotoEnd)this.dequeue();return this;}});var queue=function(elem,type,array){if(elem){type=type||"fx";var q=jQuery.data(elem,type+"queue");if(!q||array)q=jQuery.data(elem,type+"queue",jQuery.makeArray(array));}return q;};jQuery.fn.dequeue=function(type){type=type||"fx";return this.each(function(){var q=queue(this,type);q.shift();if(q.length)q[0].call(this);});};jQuery.extend({speed:function(speed,easing,fn){var opt=speed&&speed.constructor==Object?speed:{complete:fn||!fn&&easing||jQuery.isFunction(speed)&&speed,duration:speed,easing:fn&&easing||easing&&easing.constructor!=Function&&easing};opt.duration=(opt.duration&&opt.duration.constructor==Number?opt.duration:jQuery.fx.speeds[opt.duration])||jQuery.fx.speeds.def;opt.old=opt.complete;opt.complete=function(){if(opt.queue!==false)jQuery(this).dequeue();if(jQuery.isFunction(opt.old))opt.old.call(this);};return opt;},easing:{linear:function(p,n,firstNum,diff){return firstNum+diff*p;},swing:function(p,n,firstNum,diff){return((-Math.cos(p*Math.PI)/2)+0.5)*diff+firstNum;}},timers:[],timerId:null,fx:function(elem,options,prop){this.options=options;this.elem=elem;this.prop=prop;if(!options.orig)options.orig={};}});jQuery.fx.prototype={update:function(){if(this.options.step)this.options.step.call(this.elem,this.now,this);(jQuery.fx.step[this.prop]||jQuery.fx.step._default)(this);if(this.prop=="height"||this.prop=="width")this.elem.style.display="block";},cur:function(force){if(this.elem[this.prop]!=null&&this.elem.style[this.prop]==null)return this.elem[this.prop];var r=parseFloat(jQuery.css(this.elem,this.prop,force));return r&&r>-10000?r:parseFloat(jQuery.curCSS(this.elem,this.prop))||0;},custom:function(from,to,unit){this.startTime=now();this.start=from;this.end=to;this.unit=unit||this.unit||"px";this.now=this.start;this.pos=this.state=0;this.update();var self=this;function t(gotoEnd){return self.step(gotoEnd);}t.elem=this.elem;jQuery.timers.push(t);if(jQuery.timerId==null){jQuery.timerId=setInterval(function(){var timers=jQuery.timers;for(var i=0;i<timers.length;i++)if(!timers[i]())timers.splice(i--,1);if(!timers.length){clearInterval(jQuery.timerId);jQuery.timerId=null;}},13);}},show:function(){this.options.orig[this.prop]=jQuery.attr(this.elem.style,this.prop);this.options.show=true;this.custom(0,this.cur());if(this.prop=="width"||this.prop=="height")this.elem.style[this.prop]="1px";jQuery(this.elem).show();},hide:function(){this.options.orig[this.prop]=jQuery.attr(this.elem.style,this.prop);this.options.hide=true;this.custom(this.cur(),0);},step:function(gotoEnd){var t=now();if(gotoEnd||t>this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var done=true;for(var i in this.options.curAnim)if(this.options.curAnim[i]!==true)done=false;if(done){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(jQuery.css(this.elem,"display")=="none")this.elem.style.display="block";}if(this.options.hide)this.elem.style.display="none";if(this.options.hide||this.options.show)for(var p in this.options.curAnim)jQuery.attr(this.elem.style,p,this.options.orig[p]);}if(done)this.options.complete.call(this.elem);return false;}else{var n=t-this.startTime;this.state=n/this.options.duration;this.pos=jQuery.easing[this.options.easing||(jQuery.easing.swing?"swing":"linear")](this.state,n,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update();}return true;}};jQuery.extend(jQuery.fx,{speeds:{slow:600,fast:200,def:400},step:{scrollLeft:function(fx){fx.elem.scrollLeft=fx.now;},scrollTop:function(fx){fx.elem.scrollTop=fx.now;},opacity:function(fx){jQuery.attr(fx.elem.style,"opacity",fx.now);},_default:function(fx){fx.elem.style[fx.prop]=fx.now+fx.unit;}}});jQuery.fn.offset=function(){var left=0,top=0,elem=this[0],results;if(elem)with(jQuery.browser){var parent=elem.parentNode,offsetChild=elem,offsetParent=elem.offsetParent,doc=elem.ownerDocument,safari2=safari&&parseInt(version)<522&&!/adobeair/i.test(userAgent),css=jQuery.curCSS,fixed=css(elem,"position")=="fixed";if(elem.getBoundingClientRect){var box=elem.getBoundingClientRect();add(box.left+Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),box.top+Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));add(-doc.documentElement.clientLeft,-doc.documentElement.clientTop);}else{add(elem.offsetLeft,elem.offsetTop);while(offsetParent){add(offsetParent.offsetLeft,offsetParent.offsetTop);if(mozilla&&!/^t(able|d|h)$/i.test(offsetParent.tagName)||safari&&!safari2)border(offsetParent);if(!fixed&&css(offsetParent,"position")=="fixed")fixed=true;offsetChild=/^body$/i.test(offsetParent.tagName)?offsetChild:offsetParent;offsetParent=offsetParent.offsetParent;}while(parent&&parent.tagName&&!/^body|html$/i.test(parent.tagName)){if(!/^inline|table.*$/i.test(css(parent,"display")))add(-parent.scrollLeft,-parent.scrollTop);if(mozilla&&css(parent,"overflow")!="visible")border(parent);parent=parent.parentNode;}if((safari2&&(fixed||css(offsetChild,"position")=="absolute"))||(mozilla&&css(offsetChild,"position")!="absolute"))add(-doc.body.offsetLeft,-doc.body.offsetTop);if(fixed)add(Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));}results={top:top,left:left};}function border(elem){add(jQuery.curCSS(elem,"borderLeftWidth",true),jQuery.curCSS(elem,"borderTopWidth",true));}function add(l,t){left+=parseInt(l,10)||0;top+=parseInt(t,10)||0;}return results;};jQuery.fn.extend({position:function(){var left=0,top=0,results;if(this[0]){var offsetParent=this.offsetParent(),offset=this.offset(),parentOffset=/^body|html$/i.test(offsetParent[0].tagName)?{top:0,left:0}:offsetParent.offset();offset.top-=num(this,'marginTop');offset.left-=num(this,'marginLeft');parentOffset.top+=num(offsetParent,'borderTopWidth');parentOffset.left+=num(offsetParent,'borderLeftWidth');results={top:offset.top-parentOffset.top,left:offset.left-parentOffset.left};}return results;},offsetParent:function(){var offsetParent=this[0].offsetParent;while(offsetParent&&(!/^body|html$/i.test(offsetParent.tagName)&&jQuery.css(offsetParent,'position')=='static'))offsetParent=offsetParent.offsetParent;return jQuery(offsetParent);}});jQuery.each(['Left','Top'],function(i,name){var method='scroll'+name;jQuery.fn[method]=function(val){if(!this[0])return;return val!=undefined?this.each(function(){this==window||this==document?window.scrollTo(!i?val:jQuery(window).scrollLeft(),i?val:jQuery(window).scrollTop()):this[method]=val;}):this[0]==window||this[0]==document?self[i?'pageYOffset':'pageXOffset']||jQuery.boxModel&&document.documentElement[method]||document.body[method]:this[0][method];};});jQuery.each(["Height","Width"],function(i,name){var tl=i?"Left":"Top",br=i?"Right":"Bottom";jQuery.fn["inner"+name]=function(){return this[name.toLowerCase()]()+num(this,"padding"+tl)+num(this,"padding"+br);};jQuery.fn["outer"+name]=function(margin){return this["inner"+name]()+num(this,"border"+tl+"Width")+num(this,"border"+br+"Width")+(margin?num(this,"margin"+tl)+num(this,"margin"+br):0);};});})();
\ No newline at end of file diff --git a/addons/web/static/lib/jquery.superfish/js/jquery.bgiframe.min.js b/addons/web/static/lib/jquery.superfish/js/jquery.bgiframe.min.js deleted file mode 100644 index b5af5204322..00000000000 --- a/addons/web/static/lib/jquery.superfish/js/jquery.bgiframe.min.js +++ /dev/null @@ -1,10 +0,0 @@ -/* Copyright (c) 2006 Brandon Aaron (http://brandonaaron.net) - * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) - * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses. - * - * $LastChangedDate: 2007-06-19 20:25:28 -0500 (Tue, 19 Jun 2007) $ - * $Rev: 2111 $ - * - * Version 2.1 - */ -(function($){$.fn.bgIframe=$.fn.bgiframe=function(s){if($.browser.msie&&parseInt($.browser.version)<=6){s=$.extend({top:'auto',left:'auto',width:'auto',height:'auto',opacity:true,src:'javascript:false;'},s||{});var prop=function(n){return n&&n.constructor==Number?n+'px':n;},html='<iframe class="bgiframe"frameborder="0"tabindex="-1"src="'+s.src+'"'+'style="display:block;position:absolute;z-index:-1;'+(s.opacity!==false?'filter:Alpha(Opacity=\'0\');':'')+'top:'+(s.top=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderTopWidth)||0)*-1)+\'px\')':prop(s.top))+';'+'left:'+(s.left=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderLeftWidth)||0)*-1)+\'px\')':prop(s.left))+';'+'width:'+(s.width=='auto'?'expression(this.parentNode.offsetWidth+\'px\')':prop(s.width))+';'+'height:'+(s.height=='auto'?'expression(this.parentNode.offsetHeight+\'px\')':prop(s.height))+';'+'"/>';return this.each(function(){if($('> iframe.bgiframe',this).length==0)this.insertBefore(document.createElement(html),this.firstChild);});}return this;};if(!$.browser.version)$.browser.version=navigator.userAgent.toLowerCase().match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)[1];})(jQuery);
\ No newline at end of file diff --git a/addons/web/static/lib/jquery.superfish/js/superfish.js b/addons/web/static/lib/jquery.superfish/js/superfish.js deleted file mode 100644 index c6a9c7de045..00000000000 --- a/addons/web/static/lib/jquery.superfish/js/superfish.js +++ /dev/null @@ -1,121 +0,0 @@ - -/* - * Superfish v1.4.8 - jQuery menu widget - * Copyright (c) 2008 Joel Birch - * - * Dual licensed under the MIT and GPL licenses: - * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html - * - * CHANGELOG: http://users.tpg.com.au/j_birch/plugins/superfish/changelog.txt - */ - -;(function($){ - $.fn.superfish = function(op){ - - var sf = $.fn.superfish, - c = sf.c, - $arrow = $(['<span class="',c.arrowClass,'"> »</span>'].join('')), - over = function(){ - var $$ = $(this), menu = getMenu($$); - clearTimeout(menu.sfTimer); - $$.showSuperfishUl().siblings().hideSuperfishUl(); - }, - out = function(){ - var $$ = $(this), menu = getMenu($$), o = sf.op; - clearTimeout(menu.sfTimer); - menu.sfTimer=setTimeout(function(){ - o.retainPath=($.inArray($$[0],o.$path)>-1); - $$.hideSuperfishUl(); - if (o.$path.length && $$.parents(['li.',o.hoverClass].join('')).length<1){over.call(o.$path);} - },o.delay); - }, - getMenu = function($menu){ - var menu = $menu.parents(['ul.',c.menuClass,':first'].join(''))[0]; - sf.op = sf.o[menu.serial]; - return menu; - }, - addArrow = function($a){ $a.addClass(c.anchorClass).append($arrow.clone()); }; - - return this.each(function() { - var s = this.serial = sf.o.length; - var o = $.extend({},sf.defaults,op); - o.$path = $('li.'+o.pathClass,this).slice(0,o.pathLevels).each(function(){ - $(this).addClass([o.hoverClass,c.bcClass].join(' ')) - .filter('li:has(ul)').removeClass(o.pathClass); - }); - sf.o[s] = sf.op = o; - - $('li:has(ul)',this)[($.fn.hoverIntent && !o.disableHI) ? 'hoverIntent' : 'hover'](over,out).each(function() { - if (o.autoArrows) addArrow( $('>a:first-child',this) ); - }) - .not('.'+c.bcClass) - .hideSuperfishUl(); - - var $a = $('a',this); - $a.each(function(i){ - var $li = $a.eq(i).parents('li'); - $a.eq(i).focus(function(){over.call($li);}).blur(function(){out.call($li);}); - }); - o.onInit.call(this); - - }).each(function() { - var menuClasses = [c.menuClass]; - if (sf.op.dropShadows && !($.browser.msie && $.browser.version < 7)) menuClasses.push(c.shadowClass); - $(this).addClass(menuClasses.join(' ')); - }); - }; - - var sf = $.fn.superfish; - sf.o = []; - sf.op = {}; - sf.IE7fix = function(){ - var o = sf.op; - if ($.browser.msie && $.browser.version > 6 && o.dropShadows && o.animation.opacity!=undefined) - this.toggleClass(sf.c.shadowClass+'-off'); - }; - sf.c = { - bcClass : 'sf-breadcrumb', - menuClass : 'sf-js-enabled', - anchorClass : 'sf-with-ul', - arrowClass : 'sf-sub-indicator', - shadowClass : 'sf-shadow' - }; - sf.defaults = { - hoverClass : 'sfHover', - pathClass : 'overideThisToUse', - pathLevels : 1, - delay : 800, - animation : {opacity:'show'}, - speed : 'normal', - autoArrows : true, - dropShadows : true, - disableHI : false, // true disables hoverIntent detection - onInit : function(){}, // callback functions - onBeforeShow: function(){}, - onShow : function(){}, - onHide : function(){} - }; - $.fn.extend({ - hideSuperfishUl : function(){ - var o = sf.op, - not = (o.retainPath===true) ? o.$path : ''; - o.retainPath = false; - var $ul = $(['li.',o.hoverClass].join(''),this).add(this).not(not).removeClass(o.hoverClass) - .find('>ul').hide().css('visibility','hidden'); - o.onHide.call($ul); - return this; - }, - showSuperfishUl : function(){ - var o = sf.op, - sh = sf.c.shadowClass+'-off', - $ul = this.addClass(o.hoverClass) - .find('>ul:hidden').css('visibility','visible'); - sf.IE7fix.call($ul); - o.onBeforeShow.call($ul); - $ul.animate(o.animation,o.speed,function(){ sf.IE7fix.call($ul); o.onShow.call($ul); }); - return this; - } - }); - -})(jQuery); diff --git a/addons/web/static/lib/jquery.superfish/js/supersubs.js b/addons/web/static/lib/jquery.superfish/js/supersubs.js deleted file mode 100644 index 45221510fc9..00000000000 --- a/addons/web/static/lib/jquery.superfish/js/supersubs.js +++ /dev/null @@ -1,90 +0,0 @@ - -/* - * Supersubs v0.2b - jQuery plugin - * Copyright (c) 2008 Joel Birch - * - * Dual licensed under the MIT and GPL licenses: - * http://www.opensource.org/licenses/mit-license.php - * http://www.gnu.org/licenses/gpl.html - * - * - * This plugin automatically adjusts submenu widths of suckerfish-style menus to that of - * their longest list item children. If you use this, please expect bugs and report them - * to the jQuery Google Group with the word 'Superfish' in the subject line. - * - */ - -;(function($){ // $ will refer to jQuery within this closure - - $.fn.supersubs = function(options){ - var opts = $.extend({}, $.fn.supersubs.defaults, options); - // return original object to support chaining - return this.each(function() { - // cache selections - var $$ = $(this); - // support metadata - var o = $.meta ? $.extend({}, opts, $$.data()) : opts; - // get the font size of menu. - // .css('fontSize') returns various results cross-browser, so measure an em dash instead - var fontsize = $('<li id="menu-fontsize">—</li>').css({ - 'padding' : 0, - 'position' : 'absolute', - 'top' : '-999em', - 'width' : 'auto' - }).appendTo($$).width(); //clientWidth is faster, but was incorrect here - // remove em dash - $('#menu-fontsize').remove(); - // cache all ul elements - $ULs = $$.find('ul'); - // loop through each ul in menu - $ULs.each(function(i) { - // cache this ul - var $ul = $ULs.eq(i); - // get all (li) children of this ul - var $LIs = $ul.children(); - // get all anchor grand-children - var $As = $LIs.children('a'); - // force content to one line and save current float property - var liFloat = $LIs.css('white-space','nowrap').css('float'); - // remove width restrictions and floats so elements remain vertically stacked - var emWidth = $ul.add($LIs).add($As).css({ - 'float' : 'none', - 'width' : 'auto' - }) - // this ul will now be shrink-wrapped to longest li due to position:absolute - // so save its width as ems. Clientwidth is 2 times faster than .width() - thanks Dan Switzer - .end().end()[0].clientWidth / fontsize; - // add more width to ensure lines don't turn over at certain sizes in various browsers - emWidth += o.extraWidth; - // restrict to at least minWidth and at most maxWidth - if (emWidth > o.maxWidth) { emWidth = o.maxWidth; } - else if (emWidth < o.minWidth) { emWidth = o.minWidth; } - emWidth += 'em'; - // set ul to width in ems - $ul.css('width',emWidth); - // restore li floats to avoid IE bugs - // set li width to full width of this ul - // revert white-space to normal - $LIs.css({ - 'float' : liFloat, - 'width' : '100%', - 'white-space' : 'normal' - }) - // update offset position of descendant ul to reflect new width of parent - .each(function(){ - var $childUl = $('>ul',this); - var offsetDirection = $childUl.css('left')!==undefined ? 'left' : 'right'; - $childUl.css(offsetDirection,emWidth); - }); - }); - - }); - }; - // expose defaults - $.fn.supersubs.defaults = { - minWidth : 9, // requires em unit. - maxWidth : 25, // requires em unit. - extraWidth : 0 // extra width can ensure lines don't sometimes turn over due to slight browser differences in how they round-off values - }; - -})(jQuery); // plugin code ends diff --git a/addons/web/static/lib/jquery.textext/jquery.textext.css b/addons/web/static/lib/jquery.textext/jquery.textext.css new file mode 100644 index 00000000000..f180ee66184 --- /dev/null +++ b/addons/web/static/lib/jquery.textext/jquery.textext.css @@ -0,0 +1,148 @@ + +.text-core { + position: relative; +} +.text-core .text-wrap { + background: #fff; + position: absolute; +} +.text-core .text-wrap textarea, .text-core .text-wrap input { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + outline: none; + resize: none; + position: absolute; + background: none; + overflow: hidden; + margin: 0; + padding: 3px 5px 4px 5px; + white-space: nowrap; + font: 11px "lucida grande", tahoma, verdana, arial, sans-serif; + line-height: 13px; + height: auto; +} +.text-core .text-wrap .text-arrow { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + position: absolute; + top: 0; + right: 0; + width: 22px; + height: 22px; + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAOAQMAAADHWqTrAAAAA3NCSVQICAjb4U/gAAAABlBMVEX///8yXJnt8Ns4AAAACXBIWXMAAAsSAAALEgHS3X78AAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1MzmNZGAwAAABpJREFUCJljYEAF/xsY6hkY7BgYZBgYOFBkADkdAmFDagYFAAAAAElFTkSuQmCC") 50% 50% no-repeat; + cursor: pointer; + z-index: 2; +} +.text-core .text-wrap .text-dropdown { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 0; + position: absolute; + z-index: 3; + background: #fff; + border: 1px solid #9daccc; + width: 100%; + max-height: 100px; + padding: 1px; + font: 11px "lucida grande", tahoma, verdana, arial, sans-serif; + display: none; + overflow-x: hidden; + overflow-y: auto; +} +.text-core .text-wrap .text-dropdown.text-position-below { + margin-top: 1px; +} +.text-core .text-wrap .text-dropdown.text-position-above { + margin-bottom: 1px; +} +.text-core .text-wrap .text-dropdown .text-list .text-suggestion { + padding: 3px 5px; + cursor: pointer; +} +.text-core .text-wrap .text-dropdown .text-list .text-suggestion.text-selected { + color: #fff; + background: #6d84b4; +} +.text-core .text-wrap .text-focus { + -webkit-box-shadow: 0px 0px 6px #6d84b4; + -moz-box-shadow: 0px 0px 6px #6d84b4; + box-shadow: 0px 0px 6px #6d84b4; + position: absolute; + width: 100%; + height: 100%; + display: none; +} +.text-core .text-wrap .text-focus.text-show-focus { + display: block; +} +.text-core .text-wrap .text-prompt { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + position: absolute; + width: 100%; + height: 100%; + margin: 1px 0 0 2px; + font: 11px "lucida grande", tahoma, verdana, arial, sans-serif; + color: #c0c0c0; + overflow: hidden; + white-space: pre; +} +.text-core .text-wrap .text-prompt.text-hide-prompt { + display: none; +} +.text-core .text-wrap .text-tags { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + position: absolute; + width: 100%; + height: 100%; + padding: 3px 35px 3px 3px; + cursor: text; +} +.text-core .text-wrap .text-tags.text-tags-on-top { + z-index: 2; +} +.text-core .text-wrap .text-tags .text-tag { + float: left; +} +.text-core .text-wrap .text-tags .text-tag .text-button { + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + position: relative; + float: left; + border: 1px solid #9daccc; + background: #e2e6f0; + color: #000; + padding: 0px 17px 0px 3px; + margin: 0 2px 2px 0; + cursor: pointer; + height: 16px; + font: 11px "lucida grande", tahoma, verdana, arial, sans-serif; +} +.text-core .text-wrap .text-tags .text-tag .text-button a.text-remove { + position: absolute; + right: 3px; + top: 2px; + display: block; + width: 11px; + height: 11px; + background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAhCAYAAAAPm1F2AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAAB50RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNS4xqx9I6wAAAQ5JREFUOI2dlD0WwiAQhCc8L6HHgAPoASwtSYvX8BrQxtIyveYA8RppLO1jE+LwE8lzms2yH8MCj1QoaBzH+VuUYNYMS213UlvDRamtUbXb5ZyPHuDoxwGgip3ipfvGuGzPz+vZ/coDONdzFuYCO6ramQQG0DJIE1oPBBvM6e9LqaS2FwD7FWwnVoIAsOc2Xn1jDlyd8pfPBRVOBHA8cc/3yCmQqt0jcY4LuTyAF3pOYS6wI48LAm4MUrx5JthgSQJAt5LtNgAUgEMBBIC3AL2xgo58dEPfhE9wygef89FtCeC49UwltR1pQrK2qr9vNr7uRTCBF3pOYS6wI4/zdQ8MUpxPI9hgSQL0Xyio/QBt54DzsHQx6gAAAABJRU5ErkJggg==") 0 0 no-repeat; +} +.text-core .text-wrap .text-tags .text-tag .text-button a.text-remove:hover { + background-position: 0 -11px; +} +.text-core .text-wrap .text-tags .text-tag .text-button a.text-remove:active { + background-position: 0 -22px; +} diff --git a/addons/web/static/lib/jquery.textext/jquery.textext.js b/addons/web/static/lib/jquery.textext/jquery.textext.js index 294ceea8c73..94a78b6bb7a 100644 --- a/addons/web/static/lib/jquery.textext/jquery.textext.js +++ b/addons/web/static/lib/jquery.textext/jquery.textext.js @@ -2465,7 +2465,7 @@ target = $(e.target) ; - if(target.is(CSS_DOT_SUGGESTION) || target.is(CSS_DOT_LABEL)) + if($(CSS_DOT_SUGGESTION).has(target[0]) || $(CSS_DOT_LABEL).has(target[0])) self.trigger('enterKeyPress'); if (self.core().hasPlugin('tags')) @@ -4774,157 +4774,3 @@ return node; }; })(jQuery); -;(function(a){var b=document,c="getElementsByTagName",d=b[c]("head")[0]||b[c]("body")[0],e=b.createElement("style");e.innerHTML=a,d.appendChild(e)})('\n.text-core {\ - position: relative;\ -}\ -.text-core .text-wrap {\ - background: #fff;\ - position: absolute;\ -}\ -.text-core .text-wrap textarea, .text-core .text-wrap input {\ - -webkit-box-sizing: border-box;\ - -moz-box-sizing: border-box;\ - box-sizing: border-box;\ - -webkit-border-radius: 0px;\ - -moz-border-radius: 0px;\ - border-radius: 0px;\ - border: 1px solid #9daccc;\ - outline: none;\ - resize: none;\ - position: absolute;\ - z-index: 1;\ - background: none;\ - overflow: hidden;\ - margin: 0;\ - padding: 3px 5px 4px 5px;\ - white-space: nowrap;\ - font: 11px "lucida grande", tahoma, verdana, arial, sans-serif;\ - line-height: 13px;\ - height: auto;\ -}\ -\n.text-core .text-wrap .text-arrow {\ - -webkit-box-sizing: border-box;\ - -moz-box-sizing: border-box;\ - box-sizing: border-box;\ - position: absolute;\ - top: 0;\ - right: 0;\ - width: 22px;\ - height: 22px;\ - background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAOAQMAAADHWqTrAAAAA3NCSVQICAjb4U/gAAAABlBMVEX///8yXJnt8Ns4AAAACXBIWXMAAAsSAAALEgHS3X78AAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1MzmNZGAwAAABpJREFUCJljYEAF/xsY6hkY7BgYZBgYOFBkADkdAmFDagYFAAAAAElFTkSuQmCC") 50% 50% no-repeat;\ - cursor: pointer;\ - z-index: 2;\ -}\ -\n.text-core .text-wrap .text-dropdown {\ - -webkit-box-sizing: border-box;\ - -moz-box-sizing: border-box;\ - box-sizing: border-box;\ - padding: 0;\ - position: absolute;\ - z-index: 3;\ - background: #fff;\ - border: 1px solid #9daccc;\ - width: 100%;\ - max-height: 100px;\ - padding: 1px;\ - font: 11px "lucida grande", tahoma, verdana, arial, sans-serif;\ - display: none;\ - overflow-x: hidden;\ - overflow-y: auto;\ -}\ -.text-core .text-wrap .text-dropdown.text-position-below {\ - margin-top: 1px;\ -}\ -.text-core .text-wrap .text-dropdown.text-position-above {\ - margin-bottom: 1px;\ -}\ -.text-core .text-wrap .text-dropdown .text-list .text-suggestion {\ - padding: 3px 5px;\ - cursor: pointer;\ -}\ -.text-core .text-wrap .text-dropdown .text-list .text-suggestion em {\ - font-style: normal;\ - text-decoration: underline;\ -}\ -.text-core .text-wrap .text-dropdown .text-list .text-suggestion.text-selected {\ - color: #fff;\ - background: #6d84b4;\ -}\ -\n.text-core .text-wrap .text-focus {\ - -webkit-box-shadow: 0px 0px 6px #6d84b4;\ - -moz-box-shadow: 0px 0px 6px #6d84b4;\ - box-shadow: 0px 0px 6px #6d84b4;\ - position: absolute;\ - width: 100%;\ - height: 100%;\ - display: none;\ -}\ -.text-core .text-wrap .text-focus.text-show-focus {\ - display: block;\ -}\ -\n.text-core .text-wrap .text-prompt {\ - -webkit-box-sizing: border-box;\ - -moz-box-sizing: border-box;\ - box-sizing: border-box;\ - position: absolute;\ - width: 100%;\ - height: 100%;\ - margin: 1px 0 0 2px;\ - font: 11px "lucida grande", tahoma, verdana, arial, sans-serif;\ - color: #c0c0c0;\ - overflow: hidden;\ - white-space: pre;\ -}\ -.text-core .text-wrap .text-prompt.text-hide-prompt {\ - display: none;\ -}\ -\n.text-core .text-wrap .text-tags {\ - -webkit-box-sizing: border-box;\ - -moz-box-sizing: border-box;\ - box-sizing: border-box;\ - position: absolute;\ - width: 100%;\ - height: 100%;\ - padding: 3px 35px 3px 3px;\ - cursor: text;\ -}\ -.text-core .text-wrap .text-tags.text-tags-on-top {\ - z-index: 2;\ -}\ -.text-core .text-wrap .text-tags .text-tag {\ - float: left;\ -}\ -.text-core .text-wrap .text-tags .text-tag .text-button {\ - -webkit-border-radius: 2px;\ - -moz-border-radius: 2px;\ - border-radius: 2px;\ - -webkit-box-sizing: border-box;\ - -moz-box-sizing: border-box;\ - box-sizing: border-box;\ - position: relative;\ - float: left;\ - border: 1px solid #9daccc;\ - background: #e2e6f0;\ - color: #000;\ - padding: 0px 17px 0px 3px;\ - margin: 0 2px 2px 0;\ - cursor: pointer;\ - height: 16px;\ - font: 11px "lucida grande", tahoma, verdana, arial, sans-serif;\ -}\ -.text-core .text-wrap .text-tags .text-tag .text-button a.text-remove {\ - position: absolute;\ - right: 3px;\ - top: 2px;\ - display: block;\ - width: 11px;\ - height: 11px;\ - background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAAhCAYAAAAPm1F2AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAAB50RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNS4xqx9I6wAAAQ5JREFUOI2dlD0WwiAQhCc8L6HHgAPoASwtSYvX8BrQxtIyveYA8RppLO1jE+LwE8lzms2yH8MCj1QoaBzH+VuUYNYMS213UlvDRamtUbXb5ZyPHuDoxwGgip3ipfvGuGzPz+vZ/coDONdzFuYCO6ramQQG0DJIE1oPBBvM6e9LqaS2FwD7FWwnVoIAsOc2Xn1jDlyd8pfPBRVOBHA8cc/3yCmQqt0jcY4LuTyAF3pOYS6wI48LAm4MUrx5JthgSQJAt5LtNgAUgEMBBIC3AL2xgo58dEPfhE9wygef89FtCeC49UwltR1pQrK2qr9vNr7uRTCBF3pOYS6wI4/zdQ8MUpxPI9hgSQL0Xyio/QBt54DzsHQx6gAAAABJRU5ErkJggg==") 0 0 no-repeat;\ -}\ -.text-core .text-wrap .text-tags .text-tag .text-button a.text-remove:hover {\ - background-position: 0 -11px;\ -}\ -.text-core .text-wrap .text-tags .text-tag .text-button a.text-remove:active {\ - background-position: 0 -22px;\ -}\ -');
\ No newline at end of file diff --git a/addons/web/static/lib/jquery.timeago/jquery.timeago.js b/addons/web/static/lib/jquery.timeago/jquery.timeago.js new file mode 100644 index 00000000000..91ba5fd7756 --- /dev/null +++ b/addons/web/static/lib/jquery.timeago/jquery.timeago.js @@ -0,0 +1,165 @@ +/** + * Timeago is a jQuery plugin that makes it easy to support automatically + * updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago"). + * + * Please note that the library has been slightly modified for i18n's sake. + * + * @name timeago + * @version 0.11.3 + * @requires jQuery v1.2.3+ + * @author Ryan McGeary + * @license MIT License - http://www.opensource.org/licenses/mit-license.php + * + * For usage and examples, visit: + * http://timeago.yarp.com/ + * + * Copyright (c) 2008-2012, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org) + */ +(function($) { + $.timeago = function(timestamp) { + if (timestamp instanceof Date) { + return inWords(timestamp); + } else if (typeof timestamp === "string") { + return inWords($.timeago.parse(timestamp)); + } else if (typeof timestamp === "number") { + return inWords(new Date(timestamp)); + } else { + return inWords($.timeago.datetime(timestamp)); + } + }; + var $t = $.timeago; + + $.extend($.timeago, { + settings: { + refreshMillis: 60000, + allowFuture: false, + strings: { + prefixAgo: null, + prefixFromNow: null, + suffixAgo: "ago", + suffixFromNow: "from now", + seconds: "less than a minute", + minute: "about a minute", + minutes: "%d minutes", + hour: "about an hour", + hours: "about %d hours", + day: "a day", + days: "%d days", + month: "about a month", + months: "%d months", + year: "about a year", + years: "%d years", + wordSeparator: " ", + numbers: [] + }, + translator: null + }, + inWords: function(distanceMillis) { + var $l = this.settings.strings; + var prefix = $l.prefixAgo; + var suffix = $l.suffixAgo; + if (this.settings.allowFuture) { + if (distanceMillis < 0) { + prefix = $l.prefixFromNow; + suffix = $l.suffixFromNow; + } + } + + var seconds = Math.abs(distanceMillis) / 1000; + var minutes = seconds / 60; + var hours = minutes / 60; + var days = hours / 24; + var years = days / 365; + + function convert(stringOrFunction, number) { + var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction; + + // return the proper string and the numeric value that goes in it + return {'string': string, 'value': ($l.numbers && $l.numbers[number]) || number}; + } + + var stringAndNumber = seconds < 45 && convert($l.seconds, Math.round(seconds)) || + seconds < 90 && convert($l.minute, 1) || + minutes < 45 && convert($l.minutes, Math.round(minutes)) || + minutes < 90 && convert($l.hour, 1) || + hours < 24 && convert($l.hours, Math.round(hours)) || + hours < 42 && convert($l.day, 1) || + days < 30 && convert($l.days, Math.round(days)) || + days < 45 && convert($l.month, 1) || + days < 365 && convert($l.months, Math.round(days / 30)) || + years < 1.5 && convert($l.year, 1) || + convert($l.years, Math.round(years)); + + var string = stringAndNumber.string; + var value = stringAndNumber.value; + var separator = $l.wordSeparator === undefined ? " " : $l.wordSeparator; + + // compose and translate the final string + var fullString = $.trim([prefix, string, suffix].join(separator)); + var translatedString = $t.settings.translator ? + $t.settings.translator(fullString) : + fullString; + + return translatedString.replace(/%d/i, value); + }, + parse: function(iso8601) { + var s = $.trim(iso8601); + s = s.replace(/\.\d\d\d+/,""); // remove milliseconds + s = s.replace(/-/,"/").replace(/-/,"/"); + s = s.replace(/T/," ").replace(/Z/," UTC"); + s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400 + return new Date(s); + }, + datetime: function(elem) { + var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title"); + return $t.parse(iso8601); + }, + isTime: function(elem) { + // jQuery's `is()` doesn't play well with HTML5 in IE + return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time"); + } + }); + + $.fn.timeago = function() { + var self = this; + self.each(refresh); + + var $s = $t.settings; + if ($s.refreshMillis > 0) { + setInterval(function() { self.each(refresh); }, $s.refreshMillis); + } + return self; + }; + + function refresh() { + var data = prepareData(this); + if (!isNaN(data.datetime)) { + $(this).text(inWords(data.datetime)); + } + return this; + } + + function prepareData(element) { + element = $(element); + if (!element.data("timeago")) { + element.data("timeago", { datetime: $t.datetime(element) }); + var text = $.trim(element.text()); + if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) { + element.attr("title", text); + } + } + return element.data("timeago"); + } + + function inWords(date) { + return $t.inWords(distance(date)); + } + + function distance(date) { + return (new Date().getTime() - date.getTime()); + } + + // fix for IE6 suckage + document.createElement("abbr"); + document.createElement("time"); +}(jQuery)); diff --git a/addons/web/static/lib/jquery.tipsy/jquery.tipsy.js b/addons/web/static/lib/jquery.tipsy/jquery.tipsy.js index f36c9bdb43a..5929d591261 100644 --- a/addons/web/static/lib/jquery.tipsy/jquery.tipsy.js +++ b/addons/web/static/lib/jquery.tipsy/jquery.tipsy.js @@ -27,7 +27,7 @@ var $tip = this.tip(); $tip.find('.tipsy-inner')[this.options.html ? 'html' : 'text'](title); - $tip[0].className = 'tipsy'; // reset classname in case of dynamic gravity + $tip[0].className = 'tipsy openerp oe_tooltip '; // reset classname in case of dynamic gravity $tip.remove().css({top: 0, left: 0, visibility: 'hidden', display: 'block'}).prependTo(document.body); var pos = $.extend({}, this.$element.offset(), { @@ -63,7 +63,7 @@ } } - $tip.css(tp).addClass('openerp oe_tooltip tipsy-' + gravity); + $tip.css(tp).addClass('tipsy-' + gravity); $tip.find('.tipsy-arrow')[0].className = 'tipsy-arrow tipsy-arrow-' + gravity.charAt(0); if (this.options.className) { $tip.addClass(maybeCall(this.options.className, this.$element[0])); diff --git a/addons/web/static/lib/jquery.tipsy/tipsy.css b/addons/web/static/lib/jquery.tipsy/tipsy.css index 1e32ff3083b..59d0fb97b9e 100644 --- a/addons/web/static/lib/jquery.tipsy/tipsy.css +++ b/addons/web/static/lib/jquery.tipsy/tipsy.css @@ -1,4 +1,4 @@ -.tipsy { font-size: 90%; position: absolute; padding: 5px; z-index: 100000; } +.tipsy { font-size: 90%; position: absolute; padding: 5px; z-index: 100000; overflow: hidden;} .tipsy-inner { background-color: #000; color: #FFF; max-width: 500px; padding: 5px 8px 4px 8px; } /* Rounded corners */ diff --git a/addons/web/static/lib/jquery.ui.bootstrap/css/custom-theme/jquery-ui-1.8.16.custom.css b/addons/web/static/lib/jquery.ui.bootstrap/css/custom-theme/jquery-ui-1.8.16.custom.css index f77de26ed99..513fd4abe59 100644 --- a/addons/web/static/lib/jquery.ui.bootstrap/css/custom-theme/jquery-ui-1.8.16.custom.css +++ b/addons/web/static/lib/jquery.ui.bootstrap/css/custom-theme/jquery-ui-1.8.16.custom.css @@ -824,7 +824,7 @@ button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra pad background:whiteSmoke; border-bottom:1px solid #ddd; padding-bottom:0px; - color:#00438A; + color:#4c4c4c; } @@ -861,7 +861,7 @@ button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra pad border: 1px solid #ddd; border-bottom-color: #ffffff; cursor: default; - color:gray; + color:#4c4c4c; outline:none; } diff --git a/addons/web/static/lib/jquery.ui.timepicker/js/jquery-ui-timepicker-addon.js b/addons/web/static/lib/jquery.ui.timepicker/js/jquery-ui-timepicker-addon.js index f83839ee488..84888dbaaec 100644 --- a/addons/web/static/lib/jquery.ui.timepicker/js/jquery-ui-timepicker-addon.js +++ b/addons/web/static/lib/jquery.ui.timepicker/js/jquery-ui-timepicker-addon.js @@ -913,7 +913,7 @@ $.fn.extend({ tmp_args = arguments; if (typeof(o) == 'string'){ - if(o == 'getDate') + if(o == 'getDate' || o == 'widget') return $.fn.datepicker.apply($(this[0]), tmp_args); else return this.each(function() { diff --git a/addons/web/static/lib/jquery/jquery-1.6.4.js b/addons/web/static/lib/jquery/jquery-1.6.4.js deleted file mode 100644 index 2c12adbf3e8..00000000000 --- a/addons/web/static/lib/jquery/jquery-1.6.4.js +++ /dev/null @@ -1,9046 +0,0 @@ -/*! - * jQuery JavaScript Library v1.6.4 - * http://jquery.com/ - * - * Copyright 2011, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * Copyright 2011, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * - * Date: Mon Sep 12 18:54:48 2011 -0400 - */ -(function( window, undefined ) { - -// Use the correct document accordingly with window argument (sandbox) -var document = window.document, - navigator = window.navigator, - location = window.location; -var jQuery = (function() { - -// Define a local copy of jQuery -var jQuery = function( selector, context ) { - // The jQuery object is actually just the init constructor 'enhanced' - return new jQuery.fn.init( selector, context, rootjQuery ); - }, - - // Map over jQuery in case of overwrite - _jQuery = window.jQuery, - - // Map over the $ in case of overwrite - _$ = window.$, - - // A central reference to the root jQuery(document) - rootjQuery, - - // A simple way to check for HTML strings or ID strings - // Prioritize #id over <tag> to avoid XSS via location.hash (#9521) - quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, - - // Check if a string has a non-whitespace character in it - rnotwhite = /\S/, - - // Used for trimming whitespace - trimLeft = /^\s+/, - trimRight = /\s+$/, - - // Check for digits - rdigit = /\d/, - - // Match a standalone tag - rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, - - // JSON RegExp - rvalidchars = /^[\],:{}\s]*$/, - rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, - rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, - rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, - - // Useragent RegExp - rwebkit = /(webkit)[ \/]([\w.]+)/, - ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, - rmsie = /(msie) ([\w.]+)/, - rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, - - // Matches dashed string for camelizing - rdashAlpha = /-([a-z]|[0-9])/ig, - rmsPrefix = /^-ms-/, - - // Used by jQuery.camelCase as callback to replace() - fcamelCase = function( all, letter ) { - return ( letter + "" ).toUpperCase(); - }, - - // Keep a UserAgent string for use with jQuery.browser - userAgent = navigator.userAgent, - - // For matching the engine and version of the browser - browserMatch, - - // The deferred used on DOM ready - readyList, - - // The ready event handler - DOMContentLoaded, - - // Save a reference to some core methods - toString = Object.prototype.toString, - hasOwn = Object.prototype.hasOwnProperty, - push = Array.prototype.push, - slice = Array.prototype.slice, - trim = String.prototype.trim, - indexOf = Array.prototype.indexOf, - - // [[Class]] -> type pairs - class2type = {}; - -jQuery.fn = jQuery.prototype = { - constructor: jQuery, - init: function( selector, context, rootjQuery ) { - var match, elem, ret, doc; - - // Handle $(""), $(null), or $(undefined) - if ( !selector ) { - return this; - } - - // Handle $(DOMElement) - if ( selector.nodeType ) { - this.context = this[0] = selector; - this.length = 1; - return this; - } - - // The body element only exists once, optimize finding it - if ( selector === "body" && !context && document.body ) { - this.context = document; - this[0] = document.body; - this.selector = selector; - this.length = 1; - return this; - } - - // Handle HTML strings - if ( typeof selector === "string" ) { - // Are we dealing with HTML string or an ID? - if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = quickExpr.exec( selector ); - } - - // Verify a match, and that no context was specified for #id - if ( match && (match[1] || !context) ) { - - // HANDLE: $(html) -> $(array) - if ( match[1] ) { - context = context instanceof jQuery ? context[0] : context; - doc = (context ? context.ownerDocument || context : document); - - // If a single string is passed in and it's a single tag - // just do a createElement and skip the rest - ret = rsingleTag.exec( selector ); - - if ( ret ) { - if ( jQuery.isPlainObject( context ) ) { - selector = [ document.createElement( ret[1] ) ]; - jQuery.fn.attr.call( selector, context, true ); - - } else { - selector = [ doc.createElement( ret[1] ) ]; - } - - } else { - ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); - selector = (ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment).childNodes; - } - - return jQuery.merge( this, selector ); - - // HANDLE: $("#id") - } else { - elem = document.getElementById( match[2] ); - - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if ( elem && elem.parentNode ) { - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id !== match[2] ) { - return rootjQuery.find( selector ); - } - - // Otherwise, we inject the element directly into the jQuery object - this.length = 1; - this[0] = elem; - } - - this.context = document; - this.selector = selector; - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return (context || rootjQuery).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) { - return rootjQuery.ready( selector ); - } - - if (selector.selector !== undefined) { - this.selector = selector.selector; - this.context = selector.context; - } - - return jQuery.makeArray( selector, this ); - }, - - // Start with an empty selector - selector: "", - - // The current version of jQuery being used - jquery: "1.6.4", - - // The default length of a jQuery object is 0 - length: 0, - - // The number of elements contained in the matched element set - size: function() { - return this.length; - }, - - toArray: function() { - return slice.call( this, 0 ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num == null ? - - // Return a 'clean' array - this.toArray() : - - // Return just the object - ( num < 0 ? this[ this.length + num ] : this[ num ] ); - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems, name, selector ) { - // Build a new jQuery matched element set - var ret = this.constructor(); - - if ( jQuery.isArray( elems ) ) { - push.apply( ret, elems ); - - } else { - jQuery.merge( ret, elems ); - } - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - ret.context = this.context; - - if ( name === "find" ) { - ret.selector = this.selector + (this.selector ? " " : "") + selector; - } else if ( name ) { - ret.selector = this.selector + "." + name + "(" + selector + ")"; - } - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - // (You can seed the arguments with an array of args, but this is - // only used internally.) - each: function( callback, args ) { - return jQuery.each( this, callback, args ); - }, - - ready: function( fn ) { - // Attach the listeners - jQuery.bindReady(); - - // Add the callback - readyList.done( fn ); - - return this; - }, - - eq: function( i ) { - return i === -1 ? - this.slice( i ) : - this.slice( i, +i + 1 ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ), - "slice", slice.call(arguments).join(",") ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map(this, function( elem, i ) { - return callback.call( elem, i, elem ); - })); - }, - - end: function() { - return this.prevObject || this.constructor(null); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: [].sort, - splice: [].splice -}; - -// Give the init function the jQuery prototype for later instantiation -jQuery.fn.init.prototype = jQuery.fn; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[0] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - target = arguments[1] || {}; - // skip the boolean and the target - i = 2; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction(target) ) { - target = {}; - } - - // extend jQuery itself if only one argument is passed - if ( length === i ) { - target = this; - --i; - } - - for ( ; i < length; i++ ) { - // Only deal with non-null/undefined values - if ( (options = arguments[ i ]) != null ) { - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { - if ( copyIsArray ) { - copyIsArray = false; - clone = src && jQuery.isArray(src) ? src : []; - - } else { - clone = src && jQuery.isPlainObject(src) ? src : {}; - } - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend({ - noConflict: function( deep ) { - if ( window.$ === jQuery ) { - window.$ = _$; - } - - if ( deep && window.jQuery === jQuery ) { - window.jQuery = _jQuery; - } - - return jQuery; - }, - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Hold (or release) the ready event - holdReady: function( hold ) { - if ( hold ) { - jQuery.readyWait++; - } else { - jQuery.ready( true ); - } - }, - - // Handle when the DOM is ready - ready: function( wait ) { - // Either a released hold or an DOMready/load event and not yet ready - if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). - if ( !document.body ) { - return setTimeout( jQuery.ready, 1 ); - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - - // Trigger any bound ready events - if ( jQuery.fn.trigger ) { - jQuery( document ).trigger( "ready" ).unbind( "ready" ); - } - } - }, - - bindReady: function() { - if ( readyList ) { - return; - } - - readyList = jQuery._Deferred(); - - // Catch cases where $(document).ready() is called after the - // browser event has already occurred. - if ( document.readyState === "complete" ) { - // Handle it asynchronously to allow scripts the opportunity to delay ready - return setTimeout( jQuery.ready, 1 ); - } - - // Mozilla, Opera and webkit nightlies currently support this event - if ( document.addEventListener ) { - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", jQuery.ready, false ); - - // If IE event model is used - } else if ( document.attachEvent ) { - // ensure firing before onload, - // maybe late but safe also for iframes - document.attachEvent( "onreadystatechange", DOMContentLoaded ); - - // A fallback to window.onload, that will always work - window.attachEvent( "onload", jQuery.ready ); - - // If IE and not a frame - // continually check to see if the document is ready - var toplevel = false; - - try { - toplevel = window.frameElement == null; - } catch(e) {} - - if ( document.documentElement.doScroll && toplevel ) { - doScrollCheck(); - } - } - }, - - // See test/unit/core.js for details concerning isFunction. - // Since version 1.3, DOM methods and functions like alert - // aren't supported. They return false on IE (#2968). - isFunction: function( obj ) { - return jQuery.type(obj) === "function"; - }, - - isArray: Array.isArray || function( obj ) { - return jQuery.type(obj) === "array"; - }, - - // A crude way of determining if an object is a window - isWindow: function( obj ) { - return obj && typeof obj === "object" && "setInterval" in obj; - }, - - isNaN: function( obj ) { - return obj == null || !rdigit.test( obj ) || isNaN( obj ); - }, - - type: function( obj ) { - return obj == null ? - String( obj ) : - class2type[ toString.call(obj) ] || "object"; - }, - - isPlainObject: function( obj ) { - // Must be an Object. - // Because of IE, we also have to check the presence of the constructor property. - // Make sure that DOM nodes and window objects don't pass through, as well - if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { - return false; - } - - try { - // Not own constructor property must be Object - if ( obj.constructor && - !hasOwn.call(obj, "constructor") && - !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { - return false; - } - } catch ( e ) { - // IE8,9 Will throw exceptions on certain host objects #9897 - return false; - } - - // Own properties are enumerated firstly, so to speed up, - // if last one is own, then all properties are own. - - var key; - for ( key in obj ) {} - - return key === undefined || hasOwn.call( obj, key ); - }, - - isEmptyObject: function( obj ) { - for ( var name in obj ) { - return false; - } - return true; - }, - - error: function( msg ) { - throw msg; - }, - - parseJSON: function( data ) { - if ( typeof data !== "string" || !data ) { - return null; - } - - // Make sure leading/trailing whitespace is removed (IE can't handle it) - data = jQuery.trim( data ); - - // Attempt to parse using the native JSON parser first - if ( window.JSON && window.JSON.parse ) { - return window.JSON.parse( data ); - } - - // Make sure the incoming data is actual JSON - // Logic borrowed from http://json.org/json2.js - if ( rvalidchars.test( data.replace( rvalidescape, "@" ) - .replace( rvalidtokens, "]" ) - .replace( rvalidbraces, "")) ) { - - return (new Function( "return " + data ))(); - - } - jQuery.error( "Invalid JSON: " + data ); - }, - - // Cross-browser xml parsing - parseXML: function( data ) { - var xml, tmp; - try { - if ( window.DOMParser ) { // Standard - tmp = new DOMParser(); - xml = tmp.parseFromString( data , "text/xml" ); - } else { // IE - xml = new ActiveXObject( "Microsoft.XMLDOM" ); - xml.async = "false"; - xml.loadXML( data ); - } - } catch( e ) { - xml = undefined; - } - if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { - jQuery.error( "Invalid XML: " + data ); - } - return xml; - }, - - noop: function() {}, - - // Evaluates a script in a global context - // Workarounds based on findings by Jim Driscoll - // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context - globalEval: function( data ) { - if ( data && rnotwhite.test( data ) ) { - // We use execScript on Internet Explorer - // We use an anonymous function so that context is window - // rather than jQuery in Firefox - ( window.execScript || function( data ) { - window[ "eval" ].call( window, data ); - } )( data ); - } - }, - - // Convert dashed to camelCase; used by the css and data modules - // Microsoft forgot to hump their vendor prefix (#9572) - camelCase: function( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); - }, - - // args is for internal usage only - each: function( object, callback, args ) { - var name, i = 0, - length = object.length, - isObj = length === undefined || jQuery.isFunction( object ); - - if ( args ) { - if ( isObj ) { - for ( name in object ) { - if ( callback.apply( object[ name ], args ) === false ) { - break; - } - } - } else { - for ( ; i < length; ) { - if ( callback.apply( object[ i++ ], args ) === false ) { - break; - } - } - } - - // A special, fast, case for the most common use of each - } else { - if ( isObj ) { - for ( name in object ) { - if ( callback.call( object[ name ], name, object[ name ] ) === false ) { - break; - } - } - } else { - for ( ; i < length; ) { - if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { - break; - } - } - } - } - - return object; - }, - - // Use native String.trim function wherever possible - trim: trim ? - function( text ) { - return text == null ? - "" : - trim.call( text ); - } : - - // Otherwise use our own trimming functionality - function( text ) { - return text == null ? - "" : - text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); - }, - - // results is for internal usage only - makeArray: function( array, results ) { - var ret = results || []; - - if ( array != null ) { - // The window, strings (and functions) also have 'length' - // The extra typeof function check is to prevent crashes - // in Safari 2 (See: #3039) - // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 - var type = jQuery.type( array ); - - if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { - push.call( ret, array ); - } else { - jQuery.merge( ret, array ); - } - } - - return ret; - }, - - inArray: function( elem, array ) { - if ( !array ) { - return -1; - } - - if ( indexOf ) { - return indexOf.call( array, elem ); - } - - for ( var i = 0, length = array.length; i < length; i++ ) { - if ( array[ i ] === elem ) { - return i; - } - } - - return -1; - }, - - merge: function( first, second ) { - var i = first.length, - j = 0; - - if ( typeof second.length === "number" ) { - for ( var l = second.length; j < l; j++ ) { - first[ i++ ] = second[ j ]; - } - - } else { - while ( second[j] !== undefined ) { - first[ i++ ] = second[ j++ ]; - } - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, inv ) { - var ret = [], retVal; - inv = !!inv; - - // Go through the array, only saving the items - // that pass the validator function - for ( var i = 0, length = elems.length; i < length; i++ ) { - retVal = !!callback( elems[ i ], i ); - if ( inv !== retVal ) { - ret.push( elems[ i ] ); - } - } - - return ret; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var value, key, ret = [], - i = 0, - length = elems.length, - // jquery objects are treated as arrays - isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; - - // Go through the array, translating each of the items to their - if ( isArray ) { - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret[ ret.length ] = value; - } - } - - // Go through every key on the object, - } else { - for ( key in elems ) { - value = callback( elems[ key ], key, arg ); - - if ( value != null ) { - ret[ ret.length ] = value; - } - } - } - - // Flatten any nested arrays - return ret.concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // Bind a function to a context, optionally partially applying any - // arguments. - proxy: function( fn, context ) { - if ( typeof context === "string" ) { - var tmp = fn[ context ]; - context = fn; - fn = tmp; - } - - // Quick check to determine if target is callable, in the spec - // this throws a TypeError, but we will just return undefined. - if ( !jQuery.isFunction( fn ) ) { - return undefined; - } - - // Simulated bind - var args = slice.call( arguments, 2 ), - proxy = function() { - return fn.apply( context, args.concat( slice.call( arguments ) ) ); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; - - return proxy; - }, - - // Mutifunctional method to get and set values to a collection - // The value/s can optionally be executed if it's a function - access: function( elems, key, value, exec, fn, pass ) { - var length = elems.length; - - // Setting many attributes - if ( typeof key === "object" ) { - for ( var k in key ) { - jQuery.access( elems, k, key[k], exec, fn, value ); - } - return elems; - } - - // Setting one attribute - if ( value !== undefined ) { - // Optionally, function values get executed if exec is true - exec = !pass && exec && jQuery.isFunction(value); - - for ( var i = 0; i < length; i++ ) { - fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); - } - - return elems; - } - - // Getting an attribute - return length ? fn( elems[0], key ) : undefined; - }, - - now: function() { - return (new Date()).getTime(); - }, - - // Use of jQuery.browser is frowned upon. - // More details: http://docs.jquery.com/Utilities/jQuery.browser - uaMatch: function( ua ) { - ua = ua.toLowerCase(); - - var match = rwebkit.exec( ua ) || - ropera.exec( ua ) || - rmsie.exec( ua ) || - ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || - []; - - return { browser: match[1] || "", version: match[2] || "0" }; - }, - - sub: function() { - function jQuerySub( selector, context ) { - return new jQuerySub.fn.init( selector, context ); - } - jQuery.extend( true, jQuerySub, this ); - jQuerySub.superclass = this; - jQuerySub.fn = jQuerySub.prototype = this(); - jQuerySub.fn.constructor = jQuerySub; - jQuerySub.sub = this.sub; - jQuerySub.fn.init = function init( selector, context ) { - if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { - context = jQuerySub( context ); - } - - return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); - }; - jQuerySub.fn.init.prototype = jQuerySub.fn; - var rootjQuerySub = jQuerySub(document); - return jQuerySub; - }, - - browser: {} -}); - -// Populate the class2type map -jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -}); - -browserMatch = jQuery.uaMatch( userAgent ); -if ( browserMatch.browser ) { - jQuery.browser[ browserMatch.browser ] = true; - jQuery.browser.version = browserMatch.version; -} - -// Deprecated, use jQuery.browser.webkit instead -if ( jQuery.browser.webkit ) { - jQuery.browser.safari = true; -} - -// IE doesn't match non-breaking spaces with \s -if ( rnotwhite.test( "\xA0" ) ) { - trimLeft = /^[\s\xA0]+/; - trimRight = /[\s\xA0]+$/; -} - -// All jQuery objects should point back to these -rootjQuery = jQuery(document); - -// Cleanup functions for the document ready method -if ( document.addEventListener ) { - DOMContentLoaded = function() { - document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); - jQuery.ready(); - }; - -} else if ( document.attachEvent ) { - DOMContentLoaded = function() { - // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). - if ( document.readyState === "complete" ) { - document.detachEvent( "onreadystatechange", DOMContentLoaded ); - jQuery.ready(); - } - }; -} - -// The DOM ready check for Internet Explorer -function doScrollCheck() { - if ( jQuery.isReady ) { - return; - } - - try { - // If IE is used, use the trick by Diego Perini - // http://javascript.nwbox.com/IEContentLoaded/ - document.documentElement.doScroll("left"); - } catch(e) { - setTimeout( doScrollCheck, 1 ); - return; - } - - // and execute any waiting functions - jQuery.ready(); -} - -return jQuery; - -})(); - - -var // Promise methods - promiseMethods = "done fail isResolved isRejected promise then always pipe".split( " " ), - // Static reference to slice - sliceDeferred = [].slice; - -jQuery.extend({ - // Create a simple deferred (one callbacks list) - _Deferred: function() { - var // callbacks list - callbacks = [], - // stored [ context , args ] - fired, - // to avoid firing when already doing so - firing, - // flag to know if the deferred has been cancelled - cancelled, - // the deferred itself - deferred = { - - // done( f1, f2, ...) - done: function() { - if ( !cancelled ) { - var args = arguments, - i, - length, - elem, - type, - _fired; - if ( fired ) { - _fired = fired; - fired = 0; - } - for ( i = 0, length = args.length; i < length; i++ ) { - elem = args[ i ]; - type = jQuery.type( elem ); - if ( type === "array" ) { - deferred.done.apply( deferred, elem ); - } else if ( type === "function" ) { - callbacks.push( elem ); - } - } - if ( _fired ) { - deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] ); - } - } - return this; - }, - - // resolve with given context and args - resolveWith: function( context, args ) { - if ( !cancelled && !fired && !firing ) { - // make sure args are available (#8421) - args = args || []; - firing = 1; - try { - while( callbacks[ 0 ] ) { - callbacks.shift().apply( context, args ); - } - } - finally { - fired = [ context, args ]; - firing = 0; - } - } - return this; - }, - - // resolve with this as context and given arguments - resolve: function() { - deferred.resolveWith( this, arguments ); - return this; - }, - - // Has this deferred been resolved? - isResolved: function() { - return !!( firing || fired ); - }, - - // Cancel - cancel: function() { - cancelled = 1; - callbacks = []; - return this; - } - }; - - return deferred; - }, - - // Full fledged deferred (two callbacks list) - Deferred: function( func ) { - var deferred = jQuery._Deferred(), - failDeferred = jQuery._Deferred(), - promise; - // Add errorDeferred methods, then and promise - jQuery.extend( deferred, { - then: function( doneCallbacks, failCallbacks ) { - deferred.done( doneCallbacks ).fail( failCallbacks ); - return this; - }, - always: function() { - return deferred.done.apply( deferred, arguments ).fail.apply( this, arguments ); - }, - fail: failDeferred.done, - rejectWith: failDeferred.resolveWith, - reject: failDeferred.resolve, - isRejected: failDeferred.isResolved, - pipe: function( fnDone, fnFail ) { - return jQuery.Deferred(function( newDefer ) { - jQuery.each( { - done: [ fnDone, "resolve" ], - fail: [ fnFail, "reject" ] - }, function( handler, data ) { - var fn = data[ 0 ], - action = data[ 1 ], - returned; - if ( jQuery.isFunction( fn ) ) { - deferred[ handler ](function() { - returned = fn.apply( this, arguments ); - if ( returned && jQuery.isFunction( returned.promise ) ) { - returned.promise().then( newDefer.resolve, newDefer.reject ); - } else { - newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); - } - }); - } else { - deferred[ handler ]( newDefer[ action ] ); - } - }); - }).promise(); - }, - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - if ( obj == null ) { - if ( promise ) { - return promise; - } - promise = obj = {}; - } - var i = promiseMethods.length; - while( i-- ) { - obj[ promiseMethods[i] ] = deferred[ promiseMethods[i] ]; - } - return obj; - } - }); - // Make sure only one callback list will be used - deferred.done( failDeferred.cancel ).fail( deferred.cancel ); - // Unexpose cancel - delete deferred.cancel; - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - return deferred; - }, - - // Deferred helper - when: function( firstParam ) { - var args = arguments, - i = 0, - length = args.length, - count = length, - deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? - firstParam : - jQuery.Deferred(); - function resolveFunc( i ) { - return function( value ) { - args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; - if ( !( --count ) ) { - // Strange bug in FF4: - // Values changed onto the arguments object sometimes end up as undefined values - // outside the $.when method. Cloning the object into a fresh array solves the issue - deferred.resolveWith( deferred, sliceDeferred.call( args, 0 ) ); - } - }; - } - if ( length > 1 ) { - for( ; i < length; i++ ) { - if ( args[ i ] && jQuery.isFunction( args[ i ].promise ) ) { - args[ i ].promise().then( resolveFunc(i), deferred.reject ); - } else { - --count; - } - } - if ( !count ) { - deferred.resolveWith( deferred, args ); - } - } else if ( deferred !== firstParam ) { - deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); - } - return deferred.promise(); - } -}); - - - -jQuery.support = (function() { - - var div = document.createElement( "div" ), - documentElement = document.documentElement, - all, - a, - select, - opt, - input, - marginDiv, - support, - fragment, - body, - testElementParent, - testElement, - testElementStyle, - tds, - events, - eventName, - i, - isSupported; - - // Preliminary tests - div.setAttribute("className", "t"); - div.innerHTML = " <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>"; - - - all = div.getElementsByTagName( "*" ); - a = div.getElementsByTagName( "a" )[ 0 ]; - - // Can't get basic test support - if ( !all || !all.length || !a ) { - return {}; - } - - // First batch of supports tests - select = document.createElement( "select" ); - opt = select.appendChild( document.createElement("option") ); - input = div.getElementsByTagName( "input" )[ 0 ]; - - support = { - // IE strips leading whitespace when .innerHTML is used - leadingWhitespace: ( div.firstChild.nodeType === 3 ), - - // Make sure that tbody elements aren't automatically inserted - // IE will insert them into empty tables - tbody: !div.getElementsByTagName( "tbody" ).length, - - // Make sure that link elements get serialized correctly by innerHTML - // This requires a wrapper element in IE - htmlSerialize: !!div.getElementsByTagName( "link" ).length, - - // Get the style information from getAttribute - // (IE uses .cssText instead) - style: /top/.test( a.getAttribute("style") ), - - // Make sure that URLs aren't manipulated - // (IE normalizes it by default) - hrefNormalized: ( a.getAttribute( "href" ) === "/a" ), - - // Make sure that element opacity exists - // (IE uses filter instead) - // Use a regex to work around a WebKit issue. See #5145 - opacity: /^0.55$/.test( a.style.opacity ), - - // Verify style float existence - // (IE uses styleFloat instead of cssFloat) - cssFloat: !!a.style.cssFloat, - - // Make sure that if no value is specified for a checkbox - // that it defaults to "on". - // (WebKit defaults to "" instead) - checkOn: ( input.value === "on" ), - - // Make sure that a selected-by-default option has a working selected property. - // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) - optSelected: opt.selected, - - // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) - getSetAttribute: div.className !== "t", - - // Will be defined later - submitBubbles: true, - changeBubbles: true, - focusinBubbles: false, - deleteExpando: true, - noCloneEvent: true, - inlineBlockNeedsLayout: false, - shrinkWrapBlocks: false, - reliableMarginRight: true - }; - - // Make sure checked status is properly cloned - input.checked = true; - support.noCloneChecked = input.cloneNode( true ).checked; - - // Make sure that the options inside disabled selects aren't marked as disabled - // (WebKit marks them as disabled) - select.disabled = true; - support.optDisabled = !opt.disabled; - - // Test to see if it's possible to delete an expando from an element - // Fails in Internet Explorer - try { - delete div.test; - } catch( e ) { - support.deleteExpando = false; - } - - if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { - div.attachEvent( "onclick", function() { - // Cloning a node shouldn't copy over any - // bound event handlers (IE does this) - support.noCloneEvent = false; - }); - div.cloneNode( true ).fireEvent( "onclick" ); - } - - // Check if a radio maintains it's value - // after being appended to the DOM - input = document.createElement("input"); - input.value = "t"; - input.setAttribute("type", "radio"); - support.radioValue = input.value === "t"; - - input.setAttribute("checked", "checked"); - div.appendChild( input ); - fragment = document.createDocumentFragment(); - fragment.appendChild( div.firstChild ); - - // WebKit doesn't clone checked state correctly in fragments - support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; - - div.innerHTML = ""; - - // Figure out if the W3C box model works as expected - div.style.width = div.style.paddingLeft = "1px"; - - body = document.getElementsByTagName( "body" )[ 0 ]; - // We use our own, invisible, body unless the body is already present - // in which case we use a div (#9239) - testElement = document.createElement( body ? "div" : "body" ); - testElementStyle = { - visibility: "hidden", - width: 0, - height: 0, - border: 0, - margin: 0, - background: "none" - }; - if ( body ) { - jQuery.extend( testElementStyle, { - position: "absolute", - left: "-1000px", - top: "-1000px" - }); - } - for ( i in testElementStyle ) { - testElement.style[ i ] = testElementStyle[ i ]; - } - testElement.appendChild( div ); - testElementParent = body || documentElement; - testElementParent.insertBefore( testElement, testElementParent.firstChild ); - - // Check if a disconnected checkbox will retain its checked - // value of true after appended to the DOM (IE6/7) - support.appendChecked = input.checked; - - support.boxModel = div.offsetWidth === 2; - - if ( "zoom" in div.style ) { - // Check if natively block-level elements act like inline-block - // elements when setting their display to 'inline' and giving - // them layout - // (IE < 8 does this) - div.style.display = "inline"; - div.style.zoom = 1; - support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 ); - - // Check if elements with layout shrink-wrap their children - // (IE 6 does this) - div.style.display = ""; - div.innerHTML = "<div style='width:4px;'></div>"; - support.shrinkWrapBlocks = ( div.offsetWidth !== 2 ); - } - - div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>"; - tds = div.getElementsByTagName( "td" ); - - // Check if table cells still have offsetWidth/Height when they are set - // to display:none and there are still other visible table cells in a - // table row; if so, offsetWidth/Height are not reliable for use when - // determining if an element has been hidden directly using - // display:none (it is still safe to use offsets if a parent element is - // hidden; don safety goggles and see bug #4512 for more information). - // (only IE 8 fails this test) - isSupported = ( tds[ 0 ].offsetHeight === 0 ); - - tds[ 0 ].style.display = ""; - tds[ 1 ].style.display = "none"; - - // Check if empty table cells still have offsetWidth/Height - // (IE < 8 fail this test) - support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); - div.innerHTML = ""; - - // Check if div with explicit width and no margin-right incorrectly - // gets computed margin-right based on width of container. For more - // info see bug #3333 - // Fails in WebKit before Feb 2011 nightlies - // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right - if ( document.defaultView && document.defaultView.getComputedStyle ) { - marginDiv = document.createElement( "div" ); - marginDiv.style.width = "0"; - marginDiv.style.marginRight = "0"; - div.appendChild( marginDiv ); - support.reliableMarginRight = - ( parseInt( ( document.defaultView.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; - } - - // Remove the body element we added - testElement.innerHTML = ""; - testElementParent.removeChild( testElement ); - - // Technique from Juriy Zaytsev - // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ - // We only care about the case where non-standard event systems - // are used, namely in IE. Short-circuiting here helps us to - // avoid an eval call (in setAttribute) which can cause CSP - // to go haywire. See: https://developer.mozilla.org/en/Security/CSP - if ( div.attachEvent ) { - for( i in { - submit: 1, - change: 1, - focusin: 1 - } ) { - eventName = "on" + i; - isSupported = ( eventName in div ); - if ( !isSupported ) { - div.setAttribute( eventName, "return;" ); - isSupported = ( typeof div[ eventName ] === "function" ); - } - support[ i + "Bubbles" ] = isSupported; - } - } - - // Null connected elements to avoid leaks in IE - testElement = fragment = select = opt = body = marginDiv = div = input = null; - - return support; -})(); - -// Keep track of boxModel -jQuery.boxModel = jQuery.support.boxModel; - - - - -var rbrace = /^(?:\{.*\}|\[.*\])$/, - rmultiDash = /([A-Z])/g; - -jQuery.extend({ - cache: {}, - - // Please use with caution - uuid: 0, - - // Unique for each copy of jQuery on the page - // Non-digits removed to match rinlinejQuery - expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), - - // The following elements throw uncatchable exceptions if you - // attempt to add expando properties to them. - noData: { - "embed": true, - // Ban all objects except for Flash (which handle expandos) - "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", - "applet": true - }, - - hasData: function( elem ) { - elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; - - return !!elem && !isEmptyDataObject( elem ); - }, - - data: function( elem, name, data, pvt /* Internal Use Only */ ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var thisCache, ret, - internalKey = jQuery.expando, - getByName = typeof name === "string", - - // We have to handle DOM nodes and JS objects differently because IE6-7 - // can't GC object references properly across the DOM-JS boundary - isNode = elem.nodeType, - - // Only DOM nodes need the global jQuery cache; JS object data is - // attached directly to the object so GC can occur automatically - cache = isNode ? jQuery.cache : elem, - - // Only defining an ID for JS objects if its cache already exists allows - // the code to shortcut on the same path as a DOM node with no cache - id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando; - - // Avoid doing any more work than we need to when trying to get data on an - // object that has no data at all - if ( (!id || (pvt && id && (cache[ id ] && !cache[ id ][ internalKey ]))) && getByName && data === undefined ) { - return; - } - - if ( !id ) { - // Only DOM nodes need a new unique ID for each element since their data - // ends up in the global cache - if ( isNode ) { - elem[ jQuery.expando ] = id = ++jQuery.uuid; - } else { - id = jQuery.expando; - } - } - - if ( !cache[ id ] ) { - cache[ id ] = {}; - - // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery - // metadata on plain JS objects when the object is serialized using - // JSON.stringify - if ( !isNode ) { - cache[ id ].toJSON = jQuery.noop; - } - } - - // An object can be passed to jQuery.data instead of a key/value pair; this gets - // shallow copied over onto the existing cache - if ( typeof name === "object" || typeof name === "function" ) { - if ( pvt ) { - cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name); - } else { - cache[ id ] = jQuery.extend(cache[ id ], name); - } - } - - thisCache = cache[ id ]; - - // Internal jQuery data is stored in a separate object inside the object's data - // cache in order to avoid key collisions between internal data and user-defined - // data - if ( pvt ) { - if ( !thisCache[ internalKey ] ) { - thisCache[ internalKey ] = {}; - } - - thisCache = thisCache[ internalKey ]; - } - - if ( data !== undefined ) { - thisCache[ jQuery.camelCase( name ) ] = data; - } - - // TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should - // not attempt to inspect the internal events object using jQuery.data, as this - // internal data object is undocumented and subject to change. - if ( name === "events" && !thisCache[name] ) { - return thisCache[ internalKey ] && thisCache[ internalKey ].events; - } - - // Check for both converted-to-camel and non-converted data property names - // If a data property was specified - if ( getByName ) { - - // First Try to find as-is property data - ret = thisCache[ name ]; - - // Test for null|undefined property data - if ( ret == null ) { - - // Try to find the camelCased property - ret = thisCache[ jQuery.camelCase( name ) ]; - } - } else { - ret = thisCache; - } - - return ret; - }, - - removeData: function( elem, name, pvt /* Internal Use Only */ ) { - if ( !jQuery.acceptData( elem ) ) { - return; - } - - var thisCache, - - // Reference to internal data cache key - internalKey = jQuery.expando, - - isNode = elem.nodeType, - - // See jQuery.data for more information - cache = isNode ? jQuery.cache : elem, - - // See jQuery.data for more information - id = isNode ? elem[ jQuery.expando ] : jQuery.expando; - - // If there is already no cache entry for this object, there is no - // purpose in continuing - if ( !cache[ id ] ) { - return; - } - - if ( name ) { - - thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ]; - - if ( thisCache ) { - - // Support interoperable removal of hyphenated or camelcased keys - if ( !thisCache[ name ] ) { - name = jQuery.camelCase( name ); - } - - delete thisCache[ name ]; - - // If there is no data left in the cache, we want to continue - // and let the cache object itself get destroyed - if ( !isEmptyDataObject(thisCache) ) { - return; - } - } - } - - // See jQuery.data for more information - if ( pvt ) { - delete cache[ id ][ internalKey ]; - - // Don't destroy the parent cache unless the internal data object - // had been the only thing left in it - if ( !isEmptyDataObject(cache[ id ]) ) { - return; - } - } - - var internalCache = cache[ id ][ internalKey ]; - - // Browsers that fail expando deletion also refuse to delete expandos on - // the window, but it will allow it on all other JS objects; other browsers - // don't care - // Ensure that `cache` is not a window object #10080 - if ( jQuery.support.deleteExpando || !cache.setInterval ) { - delete cache[ id ]; - } else { - cache[ id ] = null; - } - - // We destroyed the entire user cache at once because it's faster than - // iterating through each key, but we need to continue to persist internal - // data if it existed - if ( internalCache ) { - cache[ id ] = {}; - // TODO: This is a hack for 1.5 ONLY. Avoids exposing jQuery - // metadata on plain JS objects when the object is serialized using - // JSON.stringify - if ( !isNode ) { - cache[ id ].toJSON = jQuery.noop; - } - - cache[ id ][ internalKey ] = internalCache; - - // Otherwise, we need to eliminate the expando on the node to avoid - // false lookups in the cache for entries that no longer exist - } else if ( isNode ) { - // IE does not allow us to delete expando properties from nodes, - // nor does it have a removeAttribute function on Document nodes; - // we must handle all of these cases - if ( jQuery.support.deleteExpando ) { - delete elem[ jQuery.expando ]; - } else if ( elem.removeAttribute ) { - elem.removeAttribute( jQuery.expando ); - } else { - elem[ jQuery.expando ] = null; - } - } - }, - - // For internal use only. - _data: function( elem, name, data ) { - return jQuery.data( elem, name, data, true ); - }, - - // A method for determining if a DOM node can handle the data expando - acceptData: function( elem ) { - if ( elem.nodeName ) { - var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; - - if ( match ) { - return !(match === true || elem.getAttribute("classid") !== match); - } - } - - return true; - } -}); - -jQuery.fn.extend({ - data: function( key, value ) { - var data = null; - - if ( typeof key === "undefined" ) { - if ( this.length ) { - data = jQuery.data( this[0] ); - - if ( this[0].nodeType === 1 ) { - var attr = this[0].attributes, name; - for ( var i = 0, l = attr.length; i < l; i++ ) { - name = attr[i].name; - - if ( name.indexOf( "data-" ) === 0 ) { - name = jQuery.camelCase( name.substring(5) ); - - dataAttr( this[0], name, data[ name ] ); - } - } - } - } - - return data; - - } else if ( typeof key === "object" ) { - return this.each(function() { - jQuery.data( this, key ); - }); - } - - var parts = key.split("."); - parts[1] = parts[1] ? "." + parts[1] : ""; - - if ( value === undefined ) { - data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); - - // Try to fetch any internally stored data first - if ( data === undefined && this.length ) { - data = jQuery.data( this[0], key ); - data = dataAttr( this[0], key, data ); - } - - return data === undefined && parts[1] ? - this.data( parts[0] ) : - data; - - } else { - return this.each(function() { - var $this = jQuery( this ), - args = [ parts[0], value ]; - - $this.triggerHandler( "setData" + parts[1] + "!", args ); - jQuery.data( this, key, value ); - $this.triggerHandler( "changeData" + parts[1] + "!", args ); - }); - } - }, - - removeData: function( key ) { - return this.each(function() { - jQuery.removeData( this, key ); - }); - } -}); - -function dataAttr( elem, key, data ) { - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - - var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); - - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = data === "true" ? true : - data === "false" ? false : - data === "null" ? null : - !jQuery.isNaN( data ) ? parseFloat( data ) : - rbrace.test( data ) ? jQuery.parseJSON( data ) : - data; - } catch( e ) {} - - // Make sure we set the data so it isn't changed later - jQuery.data( elem, key, data ); - - } else { - data = undefined; - } - } - - return data; -} - -// TODO: This is a hack for 1.5 ONLY to allow objects with a single toJSON -// property to be considered empty objects; this property always exists in -// order to make sure JSON.stringify does not expose internal metadata -function isEmptyDataObject( obj ) { - for ( var name in obj ) { - if ( name !== "toJSON" ) { - return false; - } - } - - return true; -} - - - - -function handleQueueMarkDefer( elem, type, src ) { - var deferDataKey = type + "defer", - queueDataKey = type + "queue", - markDataKey = type + "mark", - defer = jQuery.data( elem, deferDataKey, undefined, true ); - if ( defer && - ( src === "queue" || !jQuery.data( elem, queueDataKey, undefined, true ) ) && - ( src === "mark" || !jQuery.data( elem, markDataKey, undefined, true ) ) ) { - // Give room for hard-coded callbacks to fire first - // and eventually mark/queue something else on the element - setTimeout( function() { - if ( !jQuery.data( elem, queueDataKey, undefined, true ) && - !jQuery.data( elem, markDataKey, undefined, true ) ) { - jQuery.removeData( elem, deferDataKey, true ); - defer.resolve(); - } - }, 0 ); - } -} - -jQuery.extend({ - - _mark: function( elem, type ) { - if ( elem ) { - type = (type || "fx") + "mark"; - jQuery.data( elem, type, (jQuery.data(elem,type,undefined,true) || 0) + 1, true ); - } - }, - - _unmark: function( force, elem, type ) { - if ( force !== true ) { - type = elem; - elem = force; - force = false; - } - if ( elem ) { - type = type || "fx"; - var key = type + "mark", - count = force ? 0 : ( (jQuery.data( elem, key, undefined, true) || 1 ) - 1 ); - if ( count ) { - jQuery.data( elem, key, count, true ); - } else { - jQuery.removeData( elem, key, true ); - handleQueueMarkDefer( elem, type, "mark" ); - } - } - }, - - queue: function( elem, type, data ) { - if ( elem ) { - type = (type || "fx") + "queue"; - var q = jQuery.data( elem, type, undefined, true ); - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !q || jQuery.isArray(data) ) { - q = jQuery.data( elem, type, jQuery.makeArray(data), true ); - } else { - q.push( data ); - } - } - return q || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - fn = queue.shift(), - defer; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - } - - if ( fn ) { - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift("inprogress"); - } - - fn.call(elem, function() { - jQuery.dequeue(elem, type); - }); - } - - if ( !queue.length ) { - jQuery.removeData( elem, type + "queue", true ); - handleQueueMarkDefer( elem, type, "queue" ); - } - } -}); - -jQuery.fn.extend({ - queue: function( type, data ) { - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - } - - if ( data === undefined ) { - return jQuery.queue( this[0], type ); - } - return this.each(function() { - var queue = jQuery.queue( this, type, data ); - - if ( type === "fx" && queue[0] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - }); - }, - dequeue: function( type ) { - return this.each(function() { - jQuery.dequeue( this, type ); - }); - }, - // Based off of the plugin by Clint Helfers, with permission. - // http://blindsignals.com/index.php/2009/07/jquery-delay/ - delay: function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; - type = type || "fx"; - - return this.queue( type, function() { - var elem = this; - setTimeout(function() { - jQuery.dequeue( elem, type ); - }, time ); - }); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, object ) { - if ( typeof type !== "string" ) { - object = type; - type = undefined; - } - type = type || "fx"; - var defer = jQuery.Deferred(), - elements = this, - i = elements.length, - count = 1, - deferDataKey = type + "defer", - queueDataKey = type + "queue", - markDataKey = type + "mark", - tmp; - function resolve() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - } - while( i-- ) { - if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || - ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || - jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && - jQuery.data( elements[ i ], deferDataKey, jQuery._Deferred(), true ) )) { - count++; - tmp.done( resolve ); - } - } - resolve(); - return defer.promise(); - } -}); - - - - -var rclass = /[\n\t\r]/g, - rspace = /\s+/, - rreturn = /\r/g, - rtype = /^(?:button|input)$/i, - rfocusable = /^(?:button|input|object|select|textarea)$/i, - rclickable = /^a(?:rea)?$/i, - rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, - nodeHook, boolHook; - -jQuery.fn.extend({ - attr: function( name, value ) { - return jQuery.access( this, name, value, true, jQuery.attr ); - }, - - removeAttr: function( name ) { - return this.each(function() { - jQuery.removeAttr( this, name ); - }); - }, - - prop: function( name, value ) { - return jQuery.access( this, name, value, true, jQuery.prop ); - }, - - removeProp: function( name ) { - name = jQuery.propFix[ name ] || name; - return this.each(function() { - // try/catch handles cases where IE balks (such as removing a property on window) - try { - this[ name ] = undefined; - delete this[ name ]; - } catch( e ) {} - }); - }, - - addClass: function( value ) { - var classNames, i, l, elem, - setClass, c, cl; - - if ( jQuery.isFunction( value ) ) { - return this.each(function( j ) { - jQuery( this ).addClass( value.call(this, j, this.className) ); - }); - } - - if ( value && typeof value === "string" ) { - classNames = value.split( rspace ); - - for ( i = 0, l = this.length; i < l; i++ ) { - elem = this[ i ]; - - if ( elem.nodeType === 1 ) { - if ( !elem.className && classNames.length === 1 ) { - elem.className = value; - - } else { - setClass = " " + elem.className + " "; - - for ( c = 0, cl = classNames.length; c < cl; c++ ) { - if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { - setClass += classNames[ c ] + " "; - } - } - elem.className = jQuery.trim( setClass ); - } - } - } - } - - return this; - }, - - removeClass: function( value ) { - var classNames, i, l, elem, className, c, cl; - - if ( jQuery.isFunction( value ) ) { - return this.each(function( j ) { - jQuery( this ).removeClass( value.call(this, j, this.className) ); - }); - } - - if ( (value && typeof value === "string") || value === undefined ) { - classNames = (value || "").split( rspace ); - - for ( i = 0, l = this.length; i < l; i++ ) { - elem = this[ i ]; - - if ( elem.nodeType === 1 && elem.className ) { - if ( value ) { - className = (" " + elem.className + " ").replace( rclass, " " ); - for ( c = 0, cl = classNames.length; c < cl; c++ ) { - className = className.replace(" " + classNames[ c ] + " ", " "); - } - elem.className = jQuery.trim( className ); - - } else { - elem.className = ""; - } - } - } - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var type = typeof value, - isBool = typeof stateVal === "boolean"; - - if ( jQuery.isFunction( value ) ) { - return this.each(function( i ) { - jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); - }); - } - - return this.each(function() { - if ( type === "string" ) { - // toggle individual class names - var className, - i = 0, - self = jQuery( this ), - state = stateVal, - classNames = value.split( rspace ); - - while ( (className = classNames[ i++ ]) ) { - // check each className given, space seperated list - state = isBool ? state : !self.hasClass( className ); - self[ state ? "addClass" : "removeClass" ]( className ); - } - - } else if ( type === "undefined" || type === "boolean" ) { - if ( this.className ) { - // store className if set - jQuery._data( this, "__className__", this.className ); - } - - // toggle whole className - this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; - } - }); - }, - - hasClass: function( selector ) { - var className = " " + selector + " "; - for ( var i = 0, l = this.length; i < l; i++ ) { - if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { - return true; - } - } - - return false; - }, - - val: function( value ) { - var hooks, ret, - elem = this[0]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ]; - - if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { - return ret; - } - - ret = elem.value; - - return typeof ret === "string" ? - // handle most common string cases - ret.replace(rreturn, "") : - // handle cases where value is null/undef or number - ret == null ? "" : ret; - } - - return undefined; - } - - var isFunction = jQuery.isFunction( value ); - - return this.each(function( i ) { - var self = jQuery(this), val; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( isFunction ) { - val = value.call( this, i, self.val() ); - } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - } else if ( typeof val === "number" ) { - val += ""; - } else if ( jQuery.isArray( val ) ) { - val = jQuery.map(val, function ( value ) { - return value == null ? "" : value + ""; - }); - } - - hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; - } - }); - } -}); - -jQuery.extend({ - valHooks: { - option: { - get: function( elem ) { - // attributes.value is undefined in Blackberry 4.7 but - // uses .value. See #6932 - var val = elem.attributes.value; - return !val || val.specified ? elem.value : elem.text; - } - }, - select: { - get: function( elem ) { - var value, - index = elem.selectedIndex, - values = [], - options = elem.options, - one = elem.type === "select-one"; - - // Nothing was selected - if ( index < 0 ) { - return null; - } - - // Loop through all the selected options - for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { - var option = options[ i ]; - - // Don't return options that are disabled or in a disabled optgroup - if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && - (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { - - // Get the specific value for the option - value = jQuery( option ).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - // Fixes Bug #2551 -- select.val() broken in IE after form.reset() - if ( one && !values.length && options.length ) { - return jQuery( options[ index ] ).val(); - } - - return values; - }, - - set: function( elem, value ) { - var values = jQuery.makeArray( value ); - - jQuery(elem).find("option").each(function() { - this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; - }); - - if ( !values.length ) { - elem.selectedIndex = -1; - } - return values; - } - } - }, - - attrFn: { - val: true, - css: true, - html: true, - text: true, - data: true, - width: true, - height: true, - offset: true - }, - - attrFix: { - // Always normalize to ensure hook usage - tabindex: "tabIndex" - }, - - attr: function( elem, name, value, pass ) { - var nType = elem.nodeType; - - // don't get/set attributes on text, comment and attribute nodes - if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { - return undefined; - } - - if ( pass && name in jQuery.attrFn ) { - return jQuery( elem )[ name ]( value ); - } - - // Fallback to prop when attributes are not supported - if ( !("getAttribute" in elem) ) { - return jQuery.prop( elem, name, value ); - } - - var ret, hooks, - notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); - - // Normalize the name if needed - if ( notxml ) { - name = jQuery.attrFix[ name ] || name; - - hooks = jQuery.attrHooks[ name ]; - - if ( !hooks ) { - // Use boolHook for boolean attributes - if ( rboolean.test( name ) ) { - hooks = boolHook; - - // Use nodeHook if available( IE6/7 ) - } else if ( nodeHook ) { - hooks = nodeHook; - } - } - } - - if ( value !== undefined ) { - - if ( value === null ) { - jQuery.removeAttr( elem, name ); - return undefined; - - } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { - return ret; - - } else { - elem.setAttribute( name, "" + value ); - return value; - } - - } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) { - return ret; - - } else { - - ret = elem.getAttribute( name ); - - // Non-existent attributes return null, we normalize to undefined - return ret === null ? - undefined : - ret; - } - }, - - removeAttr: function( elem, name ) { - var propName; - if ( elem.nodeType === 1 ) { - name = jQuery.attrFix[ name ] || name; - - jQuery.attr( elem, name, "" ); - elem.removeAttribute( name ); - - // Set corresponding property to false for boolean attributes - if ( rboolean.test( name ) && (propName = jQuery.propFix[ name ] || name) in elem ) { - elem[ propName ] = false; - } - } - }, - - attrHooks: { - type: { - set: function( elem, value ) { - // We can't allow the type property to be changed (since it causes problems in IE) - if ( rtype.test( elem.nodeName ) && elem.parentNode ) { - jQuery.error( "type property can't be changed" ); - } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { - // Setting the type on a radio button after the value resets the value in IE6-9 - // Reset value to it's default in case type is set after value - // This is for element creation - var val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } - } - }, - // Use the value property for back compat - // Use the nodeHook for button elements in IE6/7 (#1954) - value: { - get: function( elem, name ) { - if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { - return nodeHook.get( elem, name ); - } - return name in elem ? - elem.value : - null; - }, - set: function( elem, value, name ) { - if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { - return nodeHook.set( elem, value, name ); - } - // Does not return so that setAttribute is also used - elem.value = value; - } - } - }, - - propFix: { - tabindex: "tabIndex", - readonly: "readOnly", - "for": "htmlFor", - "class": "className", - maxlength: "maxLength", - cellspacing: "cellSpacing", - cellpadding: "cellPadding", - rowspan: "rowSpan", - colspan: "colSpan", - usemap: "useMap", - frameborder: "frameBorder", - contenteditable: "contentEditable" - }, - - prop: function( elem, name, value ) { - var nType = elem.nodeType; - - // don't get/set properties on text, comment and attribute nodes - if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { - return undefined; - } - - var ret, hooks, - notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); - - if ( notxml ) { - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } - - if ( value !== undefined ) { - if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { - return ret; - - } else { - return (elem[ name ] = value); - } - - } else { - if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { - return ret; - - } else { - return elem[ name ]; - } - } - }, - - propHooks: { - tabIndex: { - get: function( elem ) { - // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set - // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ - var attributeNode = elem.getAttributeNode("tabindex"); - - return attributeNode && attributeNode.specified ? - parseInt( attributeNode.value, 10 ) : - rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? - 0 : - undefined; - } - } - } -}); - -// Add the tabindex propHook to attrHooks for back-compat -jQuery.attrHooks.tabIndex = jQuery.propHooks.tabIndex; - -// Hook for boolean attributes -boolHook = { - get: function( elem, name ) { - // Align boolean attributes with corresponding properties - // Fall back to attribute presence where some booleans are not supported - var attrNode; - return jQuery.prop( elem, name ) === true || ( attrNode = elem.getAttributeNode( name ) ) && attrNode.nodeValue !== false ? - name.toLowerCase() : - undefined; - }, - set: function( elem, value, name ) { - var propName; - if ( value === false ) { - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else { - // value is true since we know at this point it's type boolean and not false - // Set boolean attributes to the same name and set the DOM property - propName = jQuery.propFix[ name ] || name; - if ( propName in elem ) { - // Only set the IDL specifically if it already exists on the element - elem[ propName ] = true; - } - - elem.setAttribute( name, name.toLowerCase() ); - } - return name; - } -}; - -// IE6/7 do not support getting/setting some attributes with get/setAttribute -if ( !jQuery.support.getSetAttribute ) { - - // Use this for any attribute in IE6/7 - // This fixes almost every IE6/7 issue - nodeHook = jQuery.valHooks.button = { - get: function( elem, name ) { - var ret; - ret = elem.getAttributeNode( name ); - // Return undefined if nodeValue is empty string - return ret && ret.nodeValue !== "" ? - ret.nodeValue : - undefined; - }, - set: function( elem, value, name ) { - // Set the existing or create a new attribute node - var ret = elem.getAttributeNode( name ); - if ( !ret ) { - ret = document.createAttribute( name ); - elem.setAttributeNode( ret ); - } - return (ret.nodeValue = value + ""); - } - }; - - // Set width and height to auto instead of 0 on empty string( Bug #8150 ) - // This is for removals - jQuery.each([ "width", "height" ], function( i, name ) { - jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { - set: function( elem, value ) { - if ( value === "" ) { - elem.setAttribute( name, "auto" ); - return value; - } - } - }); - }); -} - - -// Some attributes require a special call on IE -if ( !jQuery.support.hrefNormalized ) { - jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { - jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { - get: function( elem ) { - var ret = elem.getAttribute( name, 2 ); - return ret === null ? undefined : ret; - } - }); - }); -} - -if ( !jQuery.support.style ) { - jQuery.attrHooks.style = { - get: function( elem ) { - // Return undefined in the case of empty string - // Normalize to lowercase since IE uppercases css property names - return elem.style.cssText.toLowerCase() || undefined; - }, - set: function( elem, value ) { - return (elem.style.cssText = "" + value); - } - }; -} - -// Safari mis-reports the default selected property of an option -// Accessing the parent's selectedIndex property fixes it -if ( !jQuery.support.optSelected ) { - jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { - get: function( elem ) { - var parent = elem.parentNode; - - if ( parent ) { - parent.selectedIndex; - - // Make sure that it also works with optgroups, see #5701 - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - return null; - } - }); -} - -// Radios and checkboxes getter/setter -if ( !jQuery.support.checkOn ) { - jQuery.each([ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - get: function( elem ) { - // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified - return elem.getAttribute("value") === null ? "on" : elem.value; - } - }; - }); -} -jQuery.each([ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { - set: function( elem, value ) { - if ( jQuery.isArray( value ) ) { - return (elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0); - } - } - }); -}); - - - - -var rnamespaces = /\.(.*)$/, - rformElems = /^(?:textarea|input|select)$/i, - rperiod = /\./g, - rspaces = / /g, - rescape = /[^\w\s.|`]/g, - fcleanup = function( nm ) { - return nm.replace(rescape, "\\$&"); - }; - -/* - * A number of helper functions used for managing events. - * Many of the ideas behind this code originated from - * Dean Edwards' addEvent library. - */ -jQuery.event = { - - // Bind an event to an element - // Original by Dean Edwards - add: function( elem, types, handler, data ) { - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - if ( handler === false ) { - handler = returnFalse; - } else if ( !handler ) { - // Fixes bug #7229. Fix recommended by jdalton - return; - } - - var handleObjIn, handleObj; - - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - } - - // Make sure that the function being executed has a unique ID - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure - var elemData = jQuery._data( elem ); - - // If no elemData is found then we must be trying to bind to one of the - // banned noData elements - if ( !elemData ) { - return; - } - - var events = elemData.events, - eventHandle = elemData.handle; - - if ( !events ) { - elemData.events = events = {}; - } - - if ( !eventHandle ) { - elemData.handle = eventHandle = function( e ) { - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? - jQuery.event.handle.apply( eventHandle.elem, arguments ) : - undefined; - }; - } - - // Add elem as a property of the handle function - // This is to prevent a memory leak with non-native events in IE. - eventHandle.elem = elem; - - // Handle multiple events separated by a space - // jQuery(...).bind("mouseover mouseout", fn); - types = types.split(" "); - - var type, i = 0, namespaces; - - while ( (type = types[ i++ ]) ) { - handleObj = handleObjIn ? - jQuery.extend({}, handleObjIn) : - { handler: handler, data: data }; - - // Namespaced event handlers - if ( type.indexOf(".") > -1 ) { - namespaces = type.split("."); - type = namespaces.shift(); - handleObj.namespace = namespaces.slice(0).sort().join("."); - - } else { - namespaces = []; - handleObj.namespace = ""; - } - - handleObj.type = type; - if ( !handleObj.guid ) { - handleObj.guid = handler.guid; - } - - // Get the current list of functions bound to this event - var handlers = events[ type ], - special = jQuery.event.special[ type ] || {}; - - // Init the event handler queue - if ( !handlers ) { - handlers = events[ type ] = []; - - // Check for a special event handler - // Only use addEventListener/attachEvent if the special - // events handler returns false - if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - // Bind the global event handler to the element - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle, false ); - - } else if ( elem.attachEvent ) { - elem.attachEvent( "on" + type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add the function to the element's handler list - handlers.push( handleObj ); - - // Keep track of which events have been used, for event optimization - jQuery.event.global[ type ] = true; - } - - // Nullify elem to prevent memory leaks in IE - elem = null; - }, - - global: {}, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, pos ) { - // don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - if ( handler === false ) { - handler = returnFalse; - } - - var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, - elemData = jQuery.hasData( elem ) && jQuery._data( elem ), - events = elemData && elemData.events; - - if ( !elemData || !events ) { - return; - } - - // types is actually an event object here - if ( types && types.type ) { - handler = types.handler; - types = types.type; - } - - // Unbind all events for the element - if ( !types || typeof types === "string" && types.charAt(0) === "." ) { - types = types || ""; - - for ( type in events ) { - jQuery.event.remove( elem, type + types ); - } - - return; - } - - // Handle multiple events separated by a space - // jQuery(...).unbind("mouseover mouseout", fn); - types = types.split(" "); - - while ( (type = types[ i++ ]) ) { - origType = type; - handleObj = null; - all = type.indexOf(".") < 0; - namespaces = []; - - if ( !all ) { - // Namespaced event handlers - namespaces = type.split("."); - type = namespaces.shift(); - - namespace = new RegExp("(^|\\.)" + - jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)"); - } - - eventType = events[ type ]; - - if ( !eventType ) { - continue; - } - - if ( !handler ) { - for ( j = 0; j < eventType.length; j++ ) { - handleObj = eventType[ j ]; - - if ( all || namespace.test( handleObj.namespace ) ) { - jQuery.event.remove( elem, origType, handleObj.handler, j ); - eventType.splice( j--, 1 ); - } - } - - continue; - } - - special = jQuery.event.special[ type ] || {}; - - for ( j = pos || 0; j < eventType.length; j++ ) { - handleObj = eventType[ j ]; - - if ( handler.guid === handleObj.guid ) { - // remove the given handler for the given type - if ( all || namespace.test( handleObj.namespace ) ) { - if ( pos == null ) { - eventType.splice( j--, 1 ); - } - - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - - if ( pos != null ) { - break; - } - } - } - - // remove generic event handler if no more handlers exist - if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { - if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { - jQuery.removeEvent( elem, type, elemData.handle ); - } - - ret = null; - delete events[ type ]; - } - } - - // Remove the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - var handle = elemData.handle; - if ( handle ) { - handle.elem = null; - } - - delete elemData.events; - delete elemData.handle; - - if ( jQuery.isEmptyObject( elemData ) ) { - jQuery.removeData( elem, undefined, true ); - } - } - }, - - // Events that are safe to short-circuit if no handlers are attached. - // Native DOM events should not be added, they may have inline handlers. - customEvent: { - "getData": true, - "setData": true, - "changeData": true - }, - - trigger: function( event, data, elem, onlyHandlers ) { - // Event object or event type - var type = event.type || event, - namespaces = [], - exclusive; - - if ( type.indexOf("!") >= 0 ) { - // Exclusive events trigger only for the exact event (no namespaces) - type = type.slice(0, -1); - exclusive = true; - } - - if ( type.indexOf(".") >= 0 ) { - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split("."); - type = namespaces.shift(); - namespaces.sort(); - } - - if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { - // No jQuery handlers for this event type, and it can't have inline handlers - return; - } - - // Caller can pass in an Event, Object, or just an event type string - event = typeof event === "object" ? - // jQuery.Event object - event[ jQuery.expando ] ? event : - // Object literal - new jQuery.Event( type, event ) : - // Just the event type (string) - new jQuery.Event( type ); - - event.type = type; - event.exclusive = exclusive; - event.namespace = namespaces.join("."); - event.namespace_re = new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)"); - - // triggerHandler() and global events don't bubble or run the default action - if ( onlyHandlers || !elem ) { - event.preventDefault(); - event.stopPropagation(); - } - - // Handle a global trigger - if ( !elem ) { - // TODO: Stop taunting the data cache; remove global events and always attach to document - jQuery.each( jQuery.cache, function() { - // internalKey variable is just used to make it easier to find - // and potentially change this stuff later; currently it just - // points to jQuery.expando - var internalKey = jQuery.expando, - internalCache = this[ internalKey ]; - if ( internalCache && internalCache.events && internalCache.events[ type ] ) { - jQuery.event.trigger( event, data, internalCache.handle.elem ); - } - }); - return; - } - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // Clean up the event in case it is being reused - event.result = undefined; - event.target = elem; - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data != null ? jQuery.makeArray( data ) : []; - data.unshift( event ); - - var cur = elem, - // IE doesn't like method names with a colon (#3533, #8272) - ontype = type.indexOf(":") < 0 ? "on" + type : ""; - - // Fire event on the current element, then bubble up the DOM tree - do { - var handle = jQuery._data( cur, "handle" ); - - event.currentTarget = cur; - if ( handle ) { - handle.apply( cur, data ); - } - - // Trigger an inline bound script - if ( ontype && jQuery.acceptData( cur ) && cur[ ontype ] && cur[ ontype ].apply( cur, data ) === false ) { - event.result = false; - event.preventDefault(); - } - - // Bubble up to document, then to window - cur = cur.parentNode || cur.ownerDocument || cur === event.target.ownerDocument && window; - } while ( cur && !event.isPropagationStopped() ); - - // If nobody prevented the default action, do it now - if ( !event.isDefaultPrevented() ) { - var old, - special = jQuery.event.special[ type ] || {}; - - if ( (!special._default || special._default.call( elem.ownerDocument, event ) === false) && - !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name name as the event. - // Can't use an .isFunction)() check here because IE6/7 fails that test. - // IE<9 dies on focus to hidden element (#1486), may want to revisit a try/catch. - try { - if ( ontype && elem[ type ] ) { - // Don't re-trigger an onFOO event when we call its FOO() method - old = elem[ ontype ]; - - if ( old ) { - elem[ ontype ] = null; - } - - jQuery.event.triggered = type; - elem[ type ](); - } - } catch ( ieError ) {} - - if ( old ) { - elem[ ontype ] = old; - } - - jQuery.event.triggered = undefined; - } - } - - return event.result; - }, - - handle: function( event ) { - event = jQuery.event.fix( event || window.event ); - // Snapshot the handlers list since a called handler may add/remove events. - var handlers = ((jQuery._data( this, "events" ) || {})[ event.type ] || []).slice(0), - run_all = !event.exclusive && !event.namespace, - args = Array.prototype.slice.call( arguments, 0 ); - - // Use the fix-ed Event rather than the (read-only) native event - args[0] = event; - event.currentTarget = this; - - for ( var j = 0, l = handlers.length; j < l; j++ ) { - var handleObj = handlers[ j ]; - - // Triggered event must 1) be non-exclusive and have no namespace, or - // 2) have namespace(s) a subset or equal to those in the bound event. - if ( run_all || event.namespace_re.test( handleObj.namespace ) ) { - // Pass in a reference to the handler function itself - // So that we can later remove it - event.handler = handleObj.handler; - event.data = handleObj.data; - event.handleObj = handleObj; - - var ret = handleObj.handler.apply( this, args ); - - if ( ret !== undefined ) { - event.result = ret; - if ( ret === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - - if ( event.isImmediatePropagationStopped() ) { - break; - } - } - } - return event.result; - }, - - props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), - - fix: function( event ) { - if ( event[ jQuery.expando ] ) { - return event; - } - - // store a copy of the original event object - // and "clone" to set read-only properties - var originalEvent = event; - event = jQuery.Event( originalEvent ); - - for ( var i = this.props.length, prop; i; ) { - prop = this.props[ --i ]; - event[ prop ] = originalEvent[ prop ]; - } - - // Fix target property, if necessary - if ( !event.target ) { - // Fixes #1925 where srcElement might not be defined either - event.target = event.srcElement || document; - } - - // check if target is a textnode (safari) - if ( event.target.nodeType === 3 ) { - event.target = event.target.parentNode; - } - - // Add relatedTarget, if necessary - if ( !event.relatedTarget && event.fromElement ) { - event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; - } - - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && event.clientX != null ) { - var eventDocument = event.target.ownerDocument || document, - doc = eventDocument.documentElement, - body = eventDocument.body; - - event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); - event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); - } - - // Add which for key events - if ( event.which == null && (event.charCode != null || event.keyCode != null) ) { - event.which = event.charCode != null ? event.charCode : event.keyCode; - } - - // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) - if ( !event.metaKey && event.ctrlKey ) { - event.metaKey = event.ctrlKey; - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if ( !event.which && event.button !== undefined ) { - event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); - } - - return event; - }, - - // Deprecated, use jQuery.guid instead - guid: 1E8, - - // Deprecated, use jQuery.proxy instead - proxy: jQuery.proxy, - - special: { - ready: { - // Make sure the ready event is setup - setup: jQuery.bindReady, - teardown: jQuery.noop - }, - - live: { - add: function( handleObj ) { - jQuery.event.add( this, - liveConvert( handleObj.origType, handleObj.selector ), - jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); - }, - - remove: function( handleObj ) { - jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj ); - } - }, - - beforeunload: { - setup: function( data, namespaces, eventHandle ) { - // We only want to do this special case on windows - if ( jQuery.isWindow( this ) ) { - this.onbeforeunload = eventHandle; - } - }, - - teardown: function( namespaces, eventHandle ) { - if ( this.onbeforeunload === eventHandle ) { - this.onbeforeunload = null; - } - } - } - } -}; - -jQuery.removeEvent = document.removeEventListener ? - function( elem, type, handle ) { - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle, false ); - } - } : - function( elem, type, handle ) { - if ( elem.detachEvent ) { - elem.detachEvent( "on" + type, handle ); - } - }; - -jQuery.Event = function( src, props ) { - // Allow instantiation without the 'new' keyword - if ( !this.preventDefault ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || - src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // timeStamp is buggy for some events on Firefox(#3843) - // So we won't rely on the native value - this.timeStamp = jQuery.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -function returnFalse() { - return false; -} -function returnTrue() { - return true; -} - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - preventDefault: function() { - this.isDefaultPrevented = returnTrue; - - var e = this.originalEvent; - if ( !e ) { - return; - } - - // if preventDefault exists run it on the original event - if ( e.preventDefault ) { - e.preventDefault(); - - // otherwise set the returnValue property of the original event to false (IE) - } else { - e.returnValue = false; - } - }, - stopPropagation: function() { - this.isPropagationStopped = returnTrue; - - var e = this.originalEvent; - if ( !e ) { - return; - } - // if stopPropagation exists run it on the original event - if ( e.stopPropagation ) { - e.stopPropagation(); - } - // otherwise set the cancelBubble property of the original event to true (IE) - e.cancelBubble = true; - }, - stopImmediatePropagation: function() { - this.isImmediatePropagationStopped = returnTrue; - this.stopPropagation(); - }, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse -}; - -// Checks if an event happened on an element within another element -// Used in jQuery.event.special.mouseenter and mouseleave handlers -var withinElement = function( event ) { - - // Check if mouse(over|out) are still within the same parent element - var related = event.relatedTarget, - inside = false, - eventType = event.type; - - event.type = event.data; - - if ( related !== this ) { - - if ( related ) { - inside = jQuery.contains( this, related ); - } - - if ( !inside ) { - - jQuery.event.handle.apply( this, arguments ); - - event.type = eventType; - } - } -}, - -// In case of event delegation, we only need to rename the event.type, -// liveHandler will take care of the rest. -delegate = function( event ) { - event.type = event.data; - jQuery.event.handle.apply( this, arguments ); -}; - -// Create mouseenter and mouseleave events -jQuery.each({ - mouseenter: "mouseover", - mouseleave: "mouseout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - setup: function( data ) { - jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); - }, - teardown: function( data ) { - jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); - } - }; -}); - -// submit delegation -if ( !jQuery.support.submitBubbles ) { - - jQuery.event.special.submit = { - setup: function( data, namespaces ) { - if ( !jQuery.nodeName( this, "form" ) ) { - jQuery.event.add(this, "click.specialSubmit", function( e ) { - // Avoid triggering error on non-existent type attribute in IE VML (#7071) - var elem = e.target, - type = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.type : ""; - - if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { - trigger( "submit", this, arguments ); - } - }); - - jQuery.event.add(this, "keypress.specialSubmit", function( e ) { - var elem = e.target, - type = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.type : ""; - - if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { - trigger( "submit", this, arguments ); - } - }); - - } else { - return false; - } - }, - - teardown: function( namespaces ) { - jQuery.event.remove( this, ".specialSubmit" ); - } - }; - -} - -// change delegation, happens here so we have bind. -if ( !jQuery.support.changeBubbles ) { - - var changeFilters, - - getVal = function( elem ) { - var type = jQuery.nodeName( elem, "input" ) ? elem.type : "", - val = elem.value; - - if ( type === "radio" || type === "checkbox" ) { - val = elem.checked; - - } else if ( type === "select-multiple" ) { - val = elem.selectedIndex > -1 ? - jQuery.map( elem.options, function( elem ) { - return elem.selected; - }).join("-") : - ""; - - } else if ( jQuery.nodeName( elem, "select" ) ) { - val = elem.selectedIndex; - } - - return val; - }, - - testChange = function testChange( e ) { - var elem = e.target, data, val; - - if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) { - return; - } - - data = jQuery._data( elem, "_change_data" ); - val = getVal(elem); - - // the current data will be also retrieved by beforeactivate - if ( e.type !== "focusout" || elem.type !== "radio" ) { - jQuery._data( elem, "_change_data", val ); - } - - if ( data === undefined || val === data ) { - return; - } - - if ( data != null || val ) { - e.type = "change"; - e.liveFired = undefined; - jQuery.event.trigger( e, arguments[1], elem ); - } - }; - - jQuery.event.special.change = { - filters: { - focusout: testChange, - - beforedeactivate: testChange, - - click: function( e ) { - var elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : ""; - - if ( type === "radio" || type === "checkbox" || jQuery.nodeName( elem, "select" ) ) { - testChange.call( this, e ); - } - }, - - // Change has to be called before submit - // Keydown will be called before keypress, which is used in submit-event delegation - keydown: function( e ) { - var elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : ""; - - if ( (e.keyCode === 13 && !jQuery.nodeName( elem, "textarea" ) ) || - (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || - type === "select-multiple" ) { - testChange.call( this, e ); - } - }, - - // Beforeactivate happens also before the previous element is blurred - // with this event you can't trigger a change event, but you can store - // information - beforeactivate: function( e ) { - var elem = e.target; - jQuery._data( elem, "_change_data", getVal(elem) ); - } - }, - - setup: function( data, namespaces ) { - if ( this.type === "file" ) { - return false; - } - - for ( var type in changeFilters ) { - jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); - } - - return rformElems.test( this.nodeName ); - }, - - teardown: function( namespaces ) { - jQuery.event.remove( this, ".specialChange" ); - - return rformElems.test( this.nodeName ); - } - }; - - changeFilters = jQuery.event.special.change.filters; - - // Handle when the input is .focus()'d - changeFilters.focus = changeFilters.beforeactivate; -} - -function trigger( type, elem, args ) { - // Piggyback on a donor event to simulate a different one. - // Fake originalEvent to avoid donor's stopPropagation, but if the - // simulated event prevents default then we do the same on the donor. - // Don't pass args or remember liveFired; they apply to the donor event. - var event = jQuery.extend( {}, args[ 0 ] ); - event.type = type; - event.originalEvent = {}; - event.liveFired = undefined; - jQuery.event.handle.call( elem, event ); - if ( event.isDefaultPrevented() ) { - args[ 0 ].preventDefault(); - } -} - -// Create "bubbling" focus and blur events -if ( !jQuery.support.focusinBubbles ) { - jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler while someone wants focusin/focusout - var attaches = 0; - - jQuery.event.special[ fix ] = { - setup: function() { - if ( attaches++ === 0 ) { - document.addEventListener( orig, handler, true ); - } - }, - teardown: function() { - if ( --attaches === 0 ) { - document.removeEventListener( orig, handler, true ); - } - } - }; - - function handler( donor ) { - // Donor event is always a native one; fix it and switch its type. - // Let focusin/out handler cancel the donor focus/blur event. - var e = jQuery.event.fix( donor ); - e.type = fix; - e.originalEvent = {}; - jQuery.event.trigger( e, null, e.target ); - if ( e.isDefaultPrevented() ) { - donor.preventDefault(); - } - } - }); -} - -jQuery.each(["bind", "one"], function( i, name ) { - jQuery.fn[ name ] = function( type, data, fn ) { - var handler; - - // Handle object literals - if ( typeof type === "object" ) { - for ( var key in type ) { - this[ name ](key, data, type[key], fn); - } - return this; - } - - if ( arguments.length === 2 || data === false ) { - fn = data; - data = undefined; - } - - if ( name === "one" ) { - handler = function( event ) { - jQuery( this ).unbind( event, handler ); - return fn.apply( this, arguments ); - }; - handler.guid = fn.guid || jQuery.guid++; - } else { - handler = fn; - } - - if ( type === "unload" && name !== "one" ) { - this.one( type, data, fn ); - - } else { - for ( var i = 0, l = this.length; i < l; i++ ) { - jQuery.event.add( this[i], type, handler, data ); - } - } - - return this; - }; -}); - -jQuery.fn.extend({ - unbind: function( type, fn ) { - // Handle object literals - if ( typeof type === "object" && !type.preventDefault ) { - for ( var key in type ) { - this.unbind(key, type[key]); - } - - } else { - for ( var i = 0, l = this.length; i < l; i++ ) { - jQuery.event.remove( this[i], type, fn ); - } - } - - return this; - }, - - delegate: function( selector, types, data, fn ) { - return this.live( types, data, fn, selector ); - }, - - undelegate: function( selector, types, fn ) { - if ( arguments.length === 0 ) { - return this.unbind( "live" ); - - } else { - return this.die( types, null, fn, selector ); - } - }, - - trigger: function( type, data ) { - return this.each(function() { - jQuery.event.trigger( type, data, this ); - }); - }, - - triggerHandler: function( type, data ) { - if ( this[0] ) { - return jQuery.event.trigger( type, data, this[0], true ); - } - }, - - toggle: function( fn ) { - // Save reference to arguments for access in closure - var args = arguments, - guid = fn.guid || jQuery.guid++, - i = 0, - toggler = function( event ) { - // Figure out which function to execute - var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; - jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); - - // Make sure that clicks stop - event.preventDefault(); - - // and execute the function - return args[ lastToggle ].apply( this, arguments ) || false; - }; - - // link all the functions, so any of them can unbind this click handler - toggler.guid = guid; - while ( i < args.length ) { - args[ i++ ].guid = guid; - } - - return this.click( toggler ); - }, - - hover: function( fnOver, fnOut ) { - return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); - } -}); - -var liveMap = { - focus: "focusin", - blur: "focusout", - mouseenter: "mouseover", - mouseleave: "mouseout" -}; - -jQuery.each(["live", "die"], function( i, name ) { - jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { - var type, i = 0, match, namespaces, preType, - selector = origSelector || this.selector, - context = origSelector ? this : jQuery( this.context ); - - if ( typeof types === "object" && !types.preventDefault ) { - for ( var key in types ) { - context[ name ]( key, data, types[key], selector ); - } - - return this; - } - - if ( name === "die" && !types && - origSelector && origSelector.charAt(0) === "." ) { - - context.unbind( origSelector ); - - return this; - } - - if ( data === false || jQuery.isFunction( data ) ) { - fn = data || returnFalse; - data = undefined; - } - - types = (types || "").split(" "); - - while ( (type = types[ i++ ]) != null ) { - match = rnamespaces.exec( type ); - namespaces = ""; - - if ( match ) { - namespaces = match[0]; - type = type.replace( rnamespaces, "" ); - } - - if ( type === "hover" ) { - types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); - continue; - } - - preType = type; - - if ( liveMap[ type ] ) { - types.push( liveMap[ type ] + namespaces ); - type = type + namespaces; - - } else { - type = (liveMap[ type ] || type) + namespaces; - } - - if ( name === "live" ) { - // bind live handler - for ( var j = 0, l = context.length; j < l; j++ ) { - jQuery.event.add( context[j], "live." + liveConvert( type, selector ), - { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); - } - - } else { - // unbind live handler - context.unbind( "live." + liveConvert( type, selector ), fn ); - } - } - - return this; - }; -}); - -function liveHandler( event ) { - var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, - elems = [], - selectors = [], - events = jQuery._data( this, "events" ); - - // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911) - if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) { - return; - } - - if ( event.namespace ) { - namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); - } - - event.liveFired = this; - - var live = events.live.slice(0); - - for ( j = 0; j < live.length; j++ ) { - handleObj = live[j]; - - if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { - selectors.push( handleObj.selector ); - - } else { - live.splice( j--, 1 ); - } - } - - match = jQuery( event.target ).closest( selectors, event.currentTarget ); - - for ( i = 0, l = match.length; i < l; i++ ) { - close = match[i]; - - for ( j = 0; j < live.length; j++ ) { - handleObj = live[j]; - - if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) && !close.elem.disabled ) { - elem = close.elem; - related = null; - - // Those two events require additional checking - if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { - event.type = handleObj.preType; - related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; - - // Make sure not to accidentally match a child element with the same selector - if ( related && jQuery.contains( elem, related ) ) { - related = elem; - } - } - - if ( !related || related !== elem ) { - elems.push({ elem: elem, handleObj: handleObj, level: close.level }); - } - } - } - } - - for ( i = 0, l = elems.length; i < l; i++ ) { - match = elems[i]; - - if ( maxLevel && match.level > maxLevel ) { - break; - } - - event.currentTarget = match.elem; - event.data = match.handleObj.data; - event.handleObj = match.handleObj; - - ret = match.handleObj.origHandler.apply( match.elem, arguments ); - - if ( ret === false || event.isPropagationStopped() ) { - maxLevel = match.level; - - if ( ret === false ) { - stop = false; - } - if ( event.isImmediatePropagationStopped() ) { - break; - } - } - } - - return stop; -} - -function liveConvert( type, selector ) { - return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspaces, "&"); -} - -jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + - "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + - "change select submit keydown keypress keyup error").split(" "), function( i, name ) { - - // Handle event binding - jQuery.fn[ name ] = function( data, fn ) { - if ( fn == null ) { - fn = data; - data = null; - } - - return arguments.length > 0 ? - this.bind( name, data, fn ) : - this.trigger( name ); - }; - - if ( jQuery.attrFn ) { - jQuery.attrFn[ name ] = true; - } -}); - - - -/*! - * Sizzle CSS Selector Engine - * Copyright 2011, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * More information: http://sizzlejs.com/ - */ -(function(){ - -var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, - done = 0, - toString = Object.prototype.toString, - hasDuplicate = false, - baseHasDuplicate = true, - rBackslash = /\\/g, - rNonWord = /\W/; - -// Here we check if the JavaScript engine is using some sort of -// optimization where it does not always call our comparision -// function. If that is the case, discard the hasDuplicate value. -// Thus far that includes Google Chrome. -[0, 0].sort(function() { - baseHasDuplicate = false; - return 0; -}); - -var Sizzle = function( selector, context, results, seed ) { - results = results || []; - context = context || document; - - var origContext = context; - - if ( context.nodeType !== 1 && context.nodeType !== 9 ) { - return []; - } - - if ( !selector || typeof selector !== "string" ) { - return results; - } - - var m, set, checkSet, extra, ret, cur, pop, i, - prune = true, - contextXML = Sizzle.isXML( context ), - parts = [], - soFar = selector; - - // Reset the position of the chunker regexp (start from head) - do { - chunker.exec( "" ); - m = chunker.exec( soFar ); - - if ( m ) { - soFar = m[3]; - - parts.push( m[1] ); - - if ( m[2] ) { - extra = m[3]; - break; - } - } - } while ( m ); - - if ( parts.length > 1 && origPOS.exec( selector ) ) { - - if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { - set = posProcess( parts[0] + parts[1], context ); - - } else { - set = Expr.relative[ parts[0] ] ? - [ context ] : - Sizzle( parts.shift(), context ); - - while ( parts.length ) { - selector = parts.shift(); - - if ( Expr.relative[ selector ] ) { - selector += parts.shift(); - } - - set = posProcess( selector, set ); - } - } - - } else { - // Take a shortcut and set the context if the root selector is an ID - // (but not if it'll be faster if the inner selector is an ID) - if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && - Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { - - ret = Sizzle.find( parts.shift(), context, contextXML ); - context = ret.expr ? - Sizzle.filter( ret.expr, ret.set )[0] : - ret.set[0]; - } - - if ( context ) { - ret = seed ? - { expr: parts.pop(), set: makeArray(seed) } : - Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); - - set = ret.expr ? - Sizzle.filter( ret.expr, ret.set ) : - ret.set; - - if ( parts.length > 0 ) { - checkSet = makeArray( set ); - - } else { - prune = false; - } - - while ( parts.length ) { - cur = parts.pop(); - pop = cur; - - if ( !Expr.relative[ cur ] ) { - cur = ""; - } else { - pop = parts.pop(); - } - - if ( pop == null ) { - pop = context; - } - - Expr.relative[ cur ]( checkSet, pop, contextXML ); - } - - } else { - checkSet = parts = []; - } - } - - if ( !checkSet ) { - checkSet = set; - } - - if ( !checkSet ) { - Sizzle.error( cur || selector ); - } - - if ( toString.call(checkSet) === "[object Array]" ) { - if ( !prune ) { - results.push.apply( results, checkSet ); - - } else if ( context && context.nodeType === 1 ) { - for ( i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { - results.push( set[i] ); - } - } - - } else { - for ( i = 0; checkSet[i] != null; i++ ) { - if ( checkSet[i] && checkSet[i].nodeType === 1 ) { - results.push( set[i] ); - } - } - } - - } else { - makeArray( checkSet, results ); - } - - if ( extra ) { - Sizzle( extra, origContext, results, seed ); - Sizzle.uniqueSort( results ); - } - - return results; -}; - -Sizzle.uniqueSort = function( results ) { - if ( sortOrder ) { - hasDuplicate = baseHasDuplicate; - results.sort( sortOrder ); - - if ( hasDuplicate ) { - for ( var i = 1; i < results.length; i++ ) { - if ( results[i] === results[ i - 1 ] ) { - results.splice( i--, 1 ); - } - } - } - } - - return results; -}; - -Sizzle.matches = function( expr, set ) { - return Sizzle( expr, null, null, set ); -}; - -Sizzle.matchesSelector = function( node, expr ) { - return Sizzle( expr, null, null, [node] ).length > 0; -}; - -Sizzle.find = function( expr, context, isXML ) { - var set; - - if ( !expr ) { - return []; - } - - for ( var i = 0, l = Expr.order.length; i < l; i++ ) { - var match, - type = Expr.order[i]; - - if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { - var left = match[1]; - match.splice( 1, 1 ); - - if ( left.substr( left.length - 1 ) !== "\\" ) { - match[1] = (match[1] || "").replace( rBackslash, "" ); - set = Expr.find[ type ]( match, context, isXML ); - - if ( set != null ) { - expr = expr.replace( Expr.match[ type ], "" ); - break; - } - } - } - } - - if ( !set ) { - set = typeof context.getElementsByTagName !== "undefined" ? - context.getElementsByTagName( "*" ) : - []; - } - - return { set: set, expr: expr }; -}; - -Sizzle.filter = function( expr, set, inplace, not ) { - var match, anyFound, - old = expr, - result = [], - curLoop = set, - isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); - - while ( expr && set.length ) { - for ( var type in Expr.filter ) { - if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { - var found, item, - filter = Expr.filter[ type ], - left = match[1]; - - anyFound = false; - - match.splice(1,1); - - if ( left.substr( left.length - 1 ) === "\\" ) { - continue; - } - - if ( curLoop === result ) { - result = []; - } - - if ( Expr.preFilter[ type ] ) { - match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); - - if ( !match ) { - anyFound = found = true; - - } else if ( match === true ) { - continue; - } - } - - if ( match ) { - for ( var i = 0; (item = curLoop[i]) != null; i++ ) { - if ( item ) { - found = filter( item, match, i, curLoop ); - var pass = not ^ !!found; - - if ( inplace && found != null ) { - if ( pass ) { - anyFound = true; - - } else { - curLoop[i] = false; - } - - } else if ( pass ) { - result.push( item ); - anyFound = true; - } - } - } - } - - if ( found !== undefined ) { - if ( !inplace ) { - curLoop = result; - } - - expr = expr.replace( Expr.match[ type ], "" ); - - if ( !anyFound ) { - return []; - } - - break; - } - } - } - - // Improper expression - if ( expr === old ) { - if ( anyFound == null ) { - Sizzle.error( expr ); - - } else { - break; - } - } - - old = expr; - } - - return curLoop; -}; - -Sizzle.error = function( msg ) { - throw "Syntax error, unrecognized expression: " + msg; -}; - -var Expr = Sizzle.selectors = { - order: [ "ID", "NAME", "TAG" ], - - match: { - ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, - CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, - NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, - ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, - TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, - CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, - POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, - PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ - }, - - leftMatch: {}, - - attrMap: { - "class": "className", - "for": "htmlFor" - }, - - attrHandle: { - href: function( elem ) { - return elem.getAttribute( "href" ); - }, - type: function( elem ) { - return elem.getAttribute( "type" ); - } - }, - - relative: { - "+": function(checkSet, part){ - var isPartStr = typeof part === "string", - isTag = isPartStr && !rNonWord.test( part ), - isPartStrNotTag = isPartStr && !isTag; - - if ( isTag ) { - part = part.toLowerCase(); - } - - for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { - if ( (elem = checkSet[i]) ) { - while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} - - checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? - elem || false : - elem === part; - } - } - - if ( isPartStrNotTag ) { - Sizzle.filter( part, checkSet, true ); - } - }, - - ">": function( checkSet, part ) { - var elem, - isPartStr = typeof part === "string", - i = 0, - l = checkSet.length; - - if ( isPartStr && !rNonWord.test( part ) ) { - part = part.toLowerCase(); - - for ( ; i < l; i++ ) { - elem = checkSet[i]; - - if ( elem ) { - var parent = elem.parentNode; - checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; - } - } - - } else { - for ( ; i < l; i++ ) { - elem = checkSet[i]; - - if ( elem ) { - checkSet[i] = isPartStr ? - elem.parentNode : - elem.parentNode === part; - } - } - - if ( isPartStr ) { - Sizzle.filter( part, checkSet, true ); - } - } - }, - - "": function(checkSet, part, isXML){ - var nodeCheck, - doneName = done++, - checkFn = dirCheck; - - if ( typeof part === "string" && !rNonWord.test( part ) ) { - part = part.toLowerCase(); - nodeCheck = part; - checkFn = dirNodeCheck; - } - - checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); - }, - - "~": function( checkSet, part, isXML ) { - var nodeCheck, - doneName = done++, - checkFn = dirCheck; - - if ( typeof part === "string" && !rNonWord.test( part ) ) { - part = part.toLowerCase(); - nodeCheck = part; - checkFn = dirNodeCheck; - } - - checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); - } - }, - - find: { - ID: function( match, context, isXML ) { - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - return m && m.parentNode ? [m] : []; - } - }, - - NAME: function( match, context ) { - if ( typeof context.getElementsByName !== "undefined" ) { - var ret = [], - results = context.getElementsByName( match[1] ); - - for ( var i = 0, l = results.length; i < l; i++ ) { - if ( results[i].getAttribute("name") === match[1] ) { - ret.push( results[i] ); - } - } - - return ret.length === 0 ? null : ret; - } - }, - - TAG: function( match, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( match[1] ); - } - } - }, - preFilter: { - CLASS: function( match, curLoop, inplace, result, not, isXML ) { - match = " " + match[1].replace( rBackslash, "" ) + " "; - - if ( isXML ) { - return match; - } - - for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { - if ( elem ) { - if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { - if ( !inplace ) { - result.push( elem ); - } - - } else if ( inplace ) { - curLoop[i] = false; - } - } - } - - return false; - }, - - ID: function( match ) { - return match[1].replace( rBackslash, "" ); - }, - - TAG: function( match, curLoop ) { - return match[1].replace( rBackslash, "" ).toLowerCase(); - }, - - CHILD: function( match ) { - if ( match[1] === "nth" ) { - if ( !match[2] ) { - Sizzle.error( match[0] ); - } - - match[2] = match[2].replace(/^\+|\s*/g, ''); - - // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' - var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( - match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || - !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); - - // calculate the numbers (first)n+(last) including if they are negative - match[2] = (test[1] + (test[2] || 1)) - 0; - match[3] = test[3] - 0; - } - else if ( match[2] ) { - Sizzle.error( match[0] ); - } - - // TODO: Move to normal caching system - match[0] = done++; - - return match; - }, - - ATTR: function( match, curLoop, inplace, result, not, isXML ) { - var name = match[1] = match[1].replace( rBackslash, "" ); - - if ( !isXML && Expr.attrMap[name] ) { - match[1] = Expr.attrMap[name]; - } - - // Handle if an un-quoted value was used - match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); - - if ( match[2] === "~=" ) { - match[4] = " " + match[4] + " "; - } - - return match; - }, - - PSEUDO: function( match, curLoop, inplace, result, not ) { - if ( match[1] === "not" ) { - // If we're dealing with a complex expression, or a simple one - if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { - match[3] = Sizzle(match[3], null, null, curLoop); - - } else { - var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); - - if ( !inplace ) { - result.push.apply( result, ret ); - } - - return false; - } - - } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { - return true; - } - - return match; - }, - - POS: function( match ) { - match.unshift( true ); - - return match; - } - }, - - filters: { - enabled: function( elem ) { - return elem.disabled === false && elem.type !== "hidden"; - }, - - disabled: function( elem ) { - return elem.disabled === true; - }, - - checked: function( elem ) { - return elem.checked === true; - }, - - selected: function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - parent: function( elem ) { - return !!elem.firstChild; - }, - - empty: function( elem ) { - return !elem.firstChild; - }, - - has: function( elem, i, match ) { - return !!Sizzle( match[3], elem ).length; - }, - - header: function( elem ) { - return (/h\d/i).test( elem.nodeName ); - }, - - text: function( elem ) { - var attr = elem.getAttribute( "type" ), type = elem.type; - // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) - // use getAttribute instead to test this case - return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null ); - }, - - radio: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type; - }, - - checkbox: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type; - }, - - file: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "file" === elem.type; - }, - - password: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "password" === elem.type; - }, - - submit: function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && "submit" === elem.type; - }, - - image: function( elem ) { - return elem.nodeName.toLowerCase() === "input" && "image" === elem.type; - }, - - reset: function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && "reset" === elem.type; - }, - - button: function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && "button" === elem.type || name === "button"; - }, - - input: function( elem ) { - return (/input|select|textarea|button/i).test( elem.nodeName ); - }, - - focus: function( elem ) { - return elem === elem.ownerDocument.activeElement; - } - }, - setFilters: { - first: function( elem, i ) { - return i === 0; - }, - - last: function( elem, i, match, array ) { - return i === array.length - 1; - }, - - even: function( elem, i ) { - return i % 2 === 0; - }, - - odd: function( elem, i ) { - return i % 2 === 1; - }, - - lt: function( elem, i, match ) { - return i < match[3] - 0; - }, - - gt: function( elem, i, match ) { - return i > match[3] - 0; - }, - - nth: function( elem, i, match ) { - return match[3] - 0 === i; - }, - - eq: function( elem, i, match ) { - return match[3] - 0 === i; - } - }, - filter: { - PSEUDO: function( elem, match, i, array ) { - var name = match[1], - filter = Expr.filters[ name ]; - - if ( filter ) { - return filter( elem, i, match, array ); - - } else if ( name === "contains" ) { - return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; - - } else if ( name === "not" ) { - var not = match[3]; - - for ( var j = 0, l = not.length; j < l; j++ ) { - if ( not[j] === elem ) { - return false; - } - } - - return true; - - } else { - Sizzle.error( name ); - } - }, - - CHILD: function( elem, match ) { - var type = match[1], - node = elem; - - switch ( type ) { - case "only": - case "first": - while ( (node = node.previousSibling) ) { - if ( node.nodeType === 1 ) { - return false; - } - } - - if ( type === "first" ) { - return true; - } - - node = elem; - - case "last": - while ( (node = node.nextSibling) ) { - if ( node.nodeType === 1 ) { - return false; - } - } - - return true; - - case "nth": - var first = match[2], - last = match[3]; - - if ( first === 1 && last === 0 ) { - return true; - } - - var doneName = match[0], - parent = elem.parentNode; - - if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { - var count = 0; - - for ( node = parent.firstChild; node; node = node.nextSibling ) { - if ( node.nodeType === 1 ) { - node.nodeIndex = ++count; - } - } - - parent.sizcache = doneName; - } - - var diff = elem.nodeIndex - last; - - if ( first === 0 ) { - return diff === 0; - - } else { - return ( diff % first === 0 && diff / first >= 0 ); - } - } - }, - - ID: function( elem, match ) { - return elem.nodeType === 1 && elem.getAttribute("id") === match; - }, - - TAG: function( elem, match ) { - return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; - }, - - CLASS: function( elem, match ) { - return (" " + (elem.className || elem.getAttribute("class")) + " ") - .indexOf( match ) > -1; - }, - - ATTR: function( elem, match ) { - var name = match[1], - result = Expr.attrHandle[ name ] ? - Expr.attrHandle[ name ]( elem ) : - elem[ name ] != null ? - elem[ name ] : - elem.getAttribute( name ), - value = result + "", - type = match[2], - check = match[4]; - - return result == null ? - type === "!=" : - type === "=" ? - value === check : - type === "*=" ? - value.indexOf(check) >= 0 : - type === "~=" ? - (" " + value + " ").indexOf(check) >= 0 : - !check ? - value && result !== false : - type === "!=" ? - value !== check : - type === "^=" ? - value.indexOf(check) === 0 : - type === "$=" ? - value.substr(value.length - check.length) === check : - type === "|=" ? - value === check || value.substr(0, check.length + 1) === check + "-" : - false; - }, - - POS: function( elem, match, i, array ) { - var name = match[2], - filter = Expr.setFilters[ name ]; - - if ( filter ) { - return filter( elem, i, match, array ); - } - } - } -}; - -var origPOS = Expr.match.POS, - fescape = function(all, num){ - return "\\" + (num - 0 + 1); - }; - -for ( var type in Expr.match ) { - Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); - Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); -} - -var makeArray = function( array, results ) { - array = Array.prototype.slice.call( array, 0 ); - - if ( results ) { - results.push.apply( results, array ); - return results; - } - - return array; -}; - -// Perform a simple check to determine if the browser is capable of -// converting a NodeList to an array using builtin methods. -// Also verifies that the returned array holds DOM nodes -// (which is not the case in the Blackberry browser) -try { - Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; - -// Provide a fallback method if it does not work -} catch( e ) { - makeArray = function( array, results ) { - var i = 0, - ret = results || []; - - if ( toString.call(array) === "[object Array]" ) { - Array.prototype.push.apply( ret, array ); - - } else { - if ( typeof array.length === "number" ) { - for ( var l = array.length; i < l; i++ ) { - ret.push( array[i] ); - } - - } else { - for ( ; array[i]; i++ ) { - ret.push( array[i] ); - } - } - } - - return ret; - }; -} - -var sortOrder, siblingCheck; - -if ( document.documentElement.compareDocumentPosition ) { - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { - return a.compareDocumentPosition ? -1 : 1; - } - - return a.compareDocumentPosition(b) & 4 ? -1 : 1; - }; - -} else { - sortOrder = function( a, b ) { - // The nodes are identical, we can exit early - if ( a === b ) { - hasDuplicate = true; - return 0; - - // Fallback to using sourceIndex (in IE) if it's available on both nodes - } else if ( a.sourceIndex && b.sourceIndex ) { - return a.sourceIndex - b.sourceIndex; - } - - var al, bl, - ap = [], - bp = [], - aup = a.parentNode, - bup = b.parentNode, - cur = aup; - - // If the nodes are siblings (or identical) we can do a quick check - if ( aup === bup ) { - return siblingCheck( a, b ); - - // If no parents were found then the nodes are disconnected - } else if ( !aup ) { - return -1; - - } else if ( !bup ) { - return 1; - } - - // Otherwise they're somewhere else in the tree so we need - // to build up a full list of the parentNodes for comparison - while ( cur ) { - ap.unshift( cur ); - cur = cur.parentNode; - } - - cur = bup; - - while ( cur ) { - bp.unshift( cur ); - cur = cur.parentNode; - } - - al = ap.length; - bl = bp.length; - - // Start walking down the tree looking for a discrepancy - for ( var i = 0; i < al && i < bl; i++ ) { - if ( ap[i] !== bp[i] ) { - return siblingCheck( ap[i], bp[i] ); - } - } - - // We ended someplace up the tree so do a sibling check - return i === al ? - siblingCheck( a, bp[i], -1 ) : - siblingCheck( ap[i], b, 1 ); - }; - - siblingCheck = function( a, b, ret ) { - if ( a === b ) { - return ret; - } - - var cur = a.nextSibling; - - while ( cur ) { - if ( cur === b ) { - return -1; - } - - cur = cur.nextSibling; - } - - return 1; - }; -} - -// Utility function for retreiving the text value of an array of DOM nodes -Sizzle.getText = function( elems ) { - var ret = "", elem; - - for ( var i = 0; elems[i]; i++ ) { - elem = elems[i]; - - // Get the text from text nodes and CDATA nodes - if ( elem.nodeType === 3 || elem.nodeType === 4 ) { - ret += elem.nodeValue; - - // Traverse everything else, except comment nodes - } else if ( elem.nodeType !== 8 ) { - ret += Sizzle.getText( elem.childNodes ); - } - } - - return ret; -}; - -// Check to see if the browser returns elements by name when -// querying by getElementById (and provide a workaround) -(function(){ - // We're going to inject a fake input element with a specified name - var form = document.createElement("div"), - id = "script" + (new Date()).getTime(), - root = document.documentElement; - - form.innerHTML = "<a name='" + id + "'/>"; - - // Inject it into the root element, check its status, and remove it quickly - root.insertBefore( form, root.firstChild ); - - // The workaround has to do additional checks after a getElementById - // Which slows things down for other browsers (hence the branching) - if ( document.getElementById( id ) ) { - Expr.find.ID = function( match, context, isXML ) { - if ( typeof context.getElementById !== "undefined" && !isXML ) { - var m = context.getElementById(match[1]); - - return m ? - m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? - [m] : - undefined : - []; - } - }; - - Expr.filter.ID = function( elem, match ) { - var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); - - return elem.nodeType === 1 && node && node.nodeValue === match; - }; - } - - root.removeChild( form ); - - // release memory in IE - root = form = null; -})(); - -(function(){ - // Check to see if the browser returns only elements - // when doing getElementsByTagName("*") - - // Create a fake element - var div = document.createElement("div"); - div.appendChild( document.createComment("") ); - - // Make sure no comments are found - if ( div.getElementsByTagName("*").length > 0 ) { - Expr.find.TAG = function( match, context ) { - var results = context.getElementsByTagName( match[1] ); - - // Filter out possible comments - if ( match[1] === "*" ) { - var tmp = []; - - for ( var i = 0; results[i]; i++ ) { - if ( results[i].nodeType === 1 ) { - tmp.push( results[i] ); - } - } - - results = tmp; - } - - return results; - }; - } - - // Check to see if an attribute returns normalized href attributes - div.innerHTML = "<a href='#'></a>"; - - if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && - div.firstChild.getAttribute("href") !== "#" ) { - - Expr.attrHandle.href = function( elem ) { - return elem.getAttribute( "href", 2 ); - }; - } - - // release memory in IE - div = null; -})(); - -if ( document.querySelectorAll ) { - (function(){ - var oldSizzle = Sizzle, - div = document.createElement("div"), - id = "__sizzle__"; - - div.innerHTML = "<p class='TEST'></p>"; - - // Safari can't handle uppercase or unicode characters when - // in quirks mode. - if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { - return; - } - - Sizzle = function( query, context, extra, seed ) { - context = context || document; - - // Only use querySelectorAll on non-XML documents - // (ID selectors don't work in non-HTML documents) - if ( !seed && !Sizzle.isXML(context) ) { - // See if we find a selector to speed up - var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query ); - - if ( match && (context.nodeType === 1 || context.nodeType === 9) ) { - // Speed-up: Sizzle("TAG") - if ( match[1] ) { - return makeArray( context.getElementsByTagName( query ), extra ); - - // Speed-up: Sizzle(".CLASS") - } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) { - return makeArray( context.getElementsByClassName( match[2] ), extra ); - } - } - - if ( context.nodeType === 9 ) { - // Speed-up: Sizzle("body") - // The body element only exists once, optimize finding it - if ( query === "body" && context.body ) { - return makeArray( [ context.body ], extra ); - - // Speed-up: Sizzle("#ID") - } else if ( match && match[3] ) { - var elem = context.getElementById( match[3] ); - - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if ( elem && elem.parentNode ) { - // Handle the case where IE and Opera return items - // by name instead of ID - if ( elem.id === match[3] ) { - return makeArray( [ elem ], extra ); - } - - } else { - return makeArray( [], extra ); - } - } - - try { - return makeArray( context.querySelectorAll(query), extra ); - } catch(qsaError) {} - - // qSA works strangely on Element-rooted queries - // We can work around this by specifying an extra ID on the root - // and working up from there (Thanks to Andrew Dupont for the technique) - // IE 8 doesn't work on object elements - } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { - var oldContext = context, - old = context.getAttribute( "id" ), - nid = old || id, - hasParent = context.parentNode, - relativeHierarchySelector = /^\s*[+~]/.test( query ); - - if ( !old ) { - context.setAttribute( "id", nid ); - } else { - nid = nid.replace( /'/g, "\\$&" ); - } - if ( relativeHierarchySelector && hasParent ) { - context = context.parentNode; - } - - try { - if ( !relativeHierarchySelector || hasParent ) { - return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra ); - } - - } catch(pseudoError) { - } finally { - if ( !old ) { - oldContext.removeAttribute( "id" ); - } - } - } - } - - return oldSizzle(query, context, extra, seed); - }; - - for ( var prop in oldSizzle ) { - Sizzle[ prop ] = oldSizzle[ prop ]; - } - - // release memory in IE - div = null; - })(); -} - -(function(){ - var html = document.documentElement, - matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector; - - if ( matches ) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9 fails this) - var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ), - pseudoWorks = false; - - try { - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( document.documentElement, "[test!='']:sizzle" ); - - } catch( pseudoError ) { - pseudoWorks = true; - } - - Sizzle.matchesSelector = function( node, expr ) { - // Make sure that attribute selectors are quoted - expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']"); - - if ( !Sizzle.isXML( node ) ) { - try { - if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) { - var ret = matches.call( node, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || !disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9, so check for that - node.document && node.document.nodeType !== 11 ) { - return ret; - } - } - } catch(e) {} - } - - return Sizzle(expr, null, null, [node]).length > 0; - }; - } -})(); - -(function(){ - var div = document.createElement("div"); - - div.innerHTML = "<div class='test e'></div><div class='test'></div>"; - - // Opera can't find a second classname (in 9.6) - // Also, make sure that getElementsByClassName actually exists - if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { - return; - } - - // Safari caches class attributes, doesn't catch changes (in 3.2) - div.lastChild.className = "e"; - - if ( div.getElementsByClassName("e").length === 1 ) { - return; - } - - Expr.order.splice(1, 0, "CLASS"); - Expr.find.CLASS = function( match, context, isXML ) { - if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { - return context.getElementsByClassName(match[1]); - } - }; - - // release memory in IE - div = null; -})(); - -function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - - if ( elem ) { - var match = false; - - elem = elem[dir]; - - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 && !isXML ){ - elem.sizcache = doneName; - elem.sizset = i; - } - - if ( elem.nodeName.toLowerCase() === cur ) { - match = elem; - break; - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } -} - -function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { - for ( var i = 0, l = checkSet.length; i < l; i++ ) { - var elem = checkSet[i]; - - if ( elem ) { - var match = false; - - elem = elem[dir]; - - while ( elem ) { - if ( elem.sizcache === doneName ) { - match = checkSet[elem.sizset]; - break; - } - - if ( elem.nodeType === 1 ) { - if ( !isXML ) { - elem.sizcache = doneName; - elem.sizset = i; - } - - if ( typeof cur !== "string" ) { - if ( elem === cur ) { - match = true; - break; - } - - } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { - match = elem; - break; - } - } - - elem = elem[dir]; - } - - checkSet[i] = match; - } - } -} - -if ( document.documentElement.contains ) { - Sizzle.contains = function( a, b ) { - return a !== b && (a.contains ? a.contains(b) : true); - }; - -} else if ( document.documentElement.compareDocumentPosition ) { - Sizzle.contains = function( a, b ) { - return !!(a.compareDocumentPosition(b) & 16); - }; - -} else { - Sizzle.contains = function() { - return false; - }; -} - -Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; - - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -var posProcess = function( selector, context ) { - var match, - tmpSet = [], - later = "", - root = context.nodeType ? [context] : context; - - // Position selectors must be done after the filter - // And so must :not(positional) so we move all PSEUDOs to the end - while ( (match = Expr.match.PSEUDO.exec( selector )) ) { - later += match[0]; - selector = selector.replace( Expr.match.PSEUDO, "" ); - } - - selector = Expr.relative[selector] ? selector + "*" : selector; - - for ( var i = 0, l = root.length; i < l; i++ ) { - Sizzle( selector, root[i], tmpSet ); - } - - return Sizzle.filter( later, tmpSet ); -}; - -// EXPOSE -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; -jQuery.expr[":"] = jQuery.expr.filters; -jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; - - -})(); - - -var runtil = /Until$/, - rparentsprev = /^(?:parents|prevUntil|prevAll)/, - // Note: This RegExp should be improved, or likely pulled from Sizzle - rmultiselector = /,/, - isSimple = /^.[^:#\[\.,]*$/, - slice = Array.prototype.slice, - POS = jQuery.expr.match.POS, - // methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend({ - find: function( selector ) { - var self = this, - i, l; - - if ( typeof selector !== "string" ) { - return jQuery( selector ).filter(function() { - for ( i = 0, l = self.length; i < l; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - }); - } - - var ret = this.pushStack( "", "find", selector ), - length, n, r; - - for ( i = 0, l = this.length; i < l; i++ ) { - length = ret.length; - jQuery.find( selector, this[i], ret ); - - if ( i > 0 ) { - // Make sure that the results are unique - for ( n = length; n < ret.length; n++ ) { - for ( r = 0; r < length; r++ ) { - if ( ret[r] === ret[n] ) { - ret.splice(n--, 1); - break; - } - } - } - } - } - - return ret; - }, - - has: function( target ) { - var targets = jQuery( target ); - return this.filter(function() { - for ( var i = 0, l = targets.length; i < l; i++ ) { - if ( jQuery.contains( this, targets[i] ) ) { - return true; - } - } - }); - }, - - not: function( selector ) { - return this.pushStack( winnow(this, selector, false), "not", selector); - }, - - filter: function( selector ) { - return this.pushStack( winnow(this, selector, true), "filter", selector ); - }, - - is: function( selector ) { - return !!selector && ( typeof selector === "string" ? - jQuery.filter( selector, this ).length > 0 : - this.filter( selector ).length > 0 ); - }, - - closest: function( selectors, context ) { - var ret = [], i, l, cur = this[0]; - - // Array - if ( jQuery.isArray( selectors ) ) { - var match, selector, - matches = {}, - level = 1; - - if ( cur && selectors.length ) { - for ( i = 0, l = selectors.length; i < l; i++ ) { - selector = selectors[i]; - - if ( !matches[ selector ] ) { - matches[ selector ] = POS.test( selector ) ? - jQuery( selector, context || this.context ) : - selector; - } - } - - while ( cur && cur.ownerDocument && cur !== context ) { - for ( selector in matches ) { - match = matches[ selector ]; - - if ( match.jquery ? match.index( cur ) > -1 : jQuery( cur ).is( match ) ) { - ret.push({ selector: selector, elem: cur, level: level }); - } - } - - cur = cur.parentNode; - level++; - } - } - - return ret; - } - - // String - var pos = POS.test( selectors ) || typeof selectors !== "string" ? - jQuery( selectors, context || this.context ) : - 0; - - for ( i = 0, l = this.length; i < l; i++ ) { - cur = this[i]; - - while ( cur ) { - if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { - ret.push( cur ); - break; - - } else { - cur = cur.parentNode; - if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) { - break; - } - } - } - } - - ret = ret.length > 1 ? jQuery.unique( ret ) : ret; - - return this.pushStack( ret, "closest", selectors ); - }, - - // Determine the position of an element within - // the matched set of elements - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1; - } - - // index in selector - if ( typeof elem === "string" ) { - return jQuery.inArray( this[0], jQuery( elem ) ); - } - - // Locate the position of the desired element - return jQuery.inArray( - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[0] : elem, this ); - }, - - add: function( selector, context ) { - var set = typeof selector === "string" ? - jQuery( selector, context ) : - jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), - all = jQuery.merge( this.get(), set ); - - return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? - all : - jQuery.unique( all ) ); - }, - - andSelf: function() { - return this.add( this.prevObject ); - } -}); - -// A painfully simple check to see if an element is disconnected -// from a document (should be improved, where feasible). -function isDisconnected( node ) { - return !node || !node.parentNode || node.parentNode.nodeType === 11; -} - -jQuery.each({ - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return jQuery.dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, i, until ) { - return jQuery.dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return jQuery.nth( elem, 2, "nextSibling" ); - }, - prev: function( elem ) { - return jQuery.nth( elem, 2, "previousSibling" ); - }, - nextAll: function( elem ) { - return jQuery.dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return jQuery.dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, i, until ) { - return jQuery.dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, i, until ) { - return jQuery.dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return jQuery.sibling( elem.parentNode.firstChild, elem ); - }, - children: function( elem ) { - return jQuery.sibling( elem.firstChild ); - }, - contents: function( elem ) { - return jQuery.nodeName( elem, "iframe" ) ? - elem.contentDocument || elem.contentWindow.document : - jQuery.makeArray( elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var ret = jQuery.map( this, fn, until ), - // The variable 'args' was introduced in - // https://github.com/jquery/jquery/commit/52a0238 - // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed. - // http://code.google.com/p/v8/issues/detail?id=1050 - args = slice.call(arguments); - - if ( !runtil.test( name ) ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - ret = jQuery.filter( selector, ret ); - } - - ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; - - if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { - ret = ret.reverse(); - } - - return this.pushStack( ret, name, args.join(",") ); - }; -}); - -jQuery.extend({ - filter: function( expr, elems, not ) { - if ( not ) { - expr = ":not(" + expr + ")"; - } - - return elems.length === 1 ? - jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : - jQuery.find.matches(expr, elems); - }, - - dir: function( elem, dir, until ) { - var matched = [], - cur = elem[ dir ]; - - while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { - if ( cur.nodeType === 1 ) { - matched.push( cur ); - } - cur = cur[dir]; - } - return matched; - }, - - nth: function( cur, result, dir, elem ) { - result = result || 1; - var num = 0; - - for ( ; cur; cur = cur[dir] ) { - if ( cur.nodeType === 1 && ++num === result ) { - break; - } - } - - return cur; - }, - - sibling: function( n, elem ) { - var r = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - r.push( n ); - } - } - - return r; - } -}); - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, keep ) { - - // Can't pass null or undefined to indexOf in Firefox 4 - // Set to 0 to skip string check - qualifier = qualifier || 0; - - if ( jQuery.isFunction( qualifier ) ) { - return jQuery.grep(elements, function( elem, i ) { - var retVal = !!qualifier.call( elem, i, elem ); - return retVal === keep; - }); - - } else if ( qualifier.nodeType ) { - return jQuery.grep(elements, function( elem, i ) { - return (elem === qualifier) === keep; - }); - - } else if ( typeof qualifier === "string" ) { - var filtered = jQuery.grep(elements, function( elem ) { - return elem.nodeType === 1; - }); - - if ( isSimple.test( qualifier ) ) { - return jQuery.filter(qualifier, filtered, !keep); - } else { - qualifier = jQuery.filter( qualifier, filtered ); - } - } - - return jQuery.grep(elements, function( elem, i ) { - return (jQuery.inArray( elem, qualifier ) >= 0) === keep; - }); -} - - - - -var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, - rleadingWhitespace = /^\s+/, - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, - rtagName = /<([\w:]+)/, - rtbody = /<tbody/i, - rhtml = /<|&#?\w+;/, - rnocache = /<(?:script|object|embed|option|style)/i, - // checked="checked" or checked - rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, - rscriptType = /\/(java|ecma)script/i, - rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)/, - wrapMap = { - option: [ 1, "<select multiple='multiple'>", "</select>" ], - legend: [ 1, "<fieldset>", "</fieldset>" ], - thead: [ 1, "<table>", "</table>" ], - tr: [ 2, "<table><tbody>", "</tbody></table>" ], - td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], - col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ], - area: [ 1, "<map>", "</map>" ], - _default: [ 0, "", "" ] - }; - -wrapMap.optgroup = wrapMap.option; -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -// IE can't serialize <link> and <script> tags normally -if ( !jQuery.support.htmlSerialize ) { - wrapMap._default = [ 1, "div<div>", "</div>" ]; -} - -jQuery.fn.extend({ - text: function( text ) { - if ( jQuery.isFunction(text) ) { - return this.each(function(i) { - var self = jQuery( this ); - - self.text( text.call(this, i, self.text()) ); - }); - } - - if ( typeof text !== "object" && text !== undefined ) { - return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); - } - - return jQuery.text( this ); - }, - - wrapAll: function( html ) { - if ( jQuery.isFunction( html ) ) { - return this.each(function(i) { - jQuery(this).wrapAll( html.call(this, i) ); - }); - } - - if ( this[0] ) { - // The elements to wrap the target around - var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); - - if ( this[0].parentNode ) { - wrap.insertBefore( this[0] ); - } - - wrap.map(function() { - var elem = this; - - while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { - elem = elem.firstChild; - } - - return elem; - }).append( this ); - } - - return this; - }, - - wrapInner: function( html ) { - if ( jQuery.isFunction( html ) ) { - return this.each(function(i) { - jQuery(this).wrapInner( html.call(this, i) ); - }); - } - - return this.each(function() { - var self = jQuery( this ), - contents = self.contents(); - - if ( contents.length ) { - contents.wrapAll( html ); - - } else { - self.append( html ); - } - }); - }, - - wrap: function( html ) { - return this.each(function() { - jQuery( this ).wrapAll( html ); - }); - }, - - unwrap: function() { - return this.parent().each(function() { - if ( !jQuery.nodeName( this, "body" ) ) { - jQuery( this ).replaceWith( this.childNodes ); - } - }).end(); - }, - - append: function() { - return this.domManip(arguments, true, function( elem ) { - if ( this.nodeType === 1 ) { - this.appendChild( elem ); - } - }); - }, - - prepend: function() { - return this.domManip(arguments, true, function( elem ) { - if ( this.nodeType === 1 ) { - this.insertBefore( elem, this.firstChild ); - } - }); - }, - - before: function() { - if ( this[0] && this[0].parentNode ) { - return this.domManip(arguments, false, function( elem ) { - this.parentNode.insertBefore( elem, this ); - }); - } else if ( arguments.length ) { - var set = jQuery(arguments[0]); - set.push.apply( set, this.toArray() ); - return this.pushStack( set, "before", arguments ); - } - }, - - after: function() { - if ( this[0] && this[0].parentNode ) { - return this.domManip(arguments, false, function( elem ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - }); - } else if ( arguments.length ) { - var set = this.pushStack( this, "after", arguments ); - set.push.apply( set, jQuery(arguments[0]).toArray() ); - return set; - } - }, - - // keepData is for internal use only--do not document - remove: function( selector, keepData ) { - for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { - if ( !selector || jQuery.filter( selector, [ elem ] ).length ) { - if ( !keepData && elem.nodeType === 1 ) { - jQuery.cleanData( elem.getElementsByTagName("*") ); - jQuery.cleanData( [ elem ] ); - } - - if ( elem.parentNode ) { - elem.parentNode.removeChild( elem ); - } - } - } - - return this; - }, - - empty: function() { - for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( elem.getElementsByTagName("*") ); - } - - // Remove any remaining nodes - while ( elem.firstChild ) { - elem.removeChild( elem.firstChild ); - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function () { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - }); - }, - - html: function( value ) { - if ( value === undefined ) { - return this[0] && this[0].nodeType === 1 ? - this[0].innerHTML.replace(rinlinejQuery, "") : - null; - - // See if we can take a shortcut and just use innerHTML - } else if ( typeof value === "string" && !rnocache.test( value ) && - (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) && - !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) { - - value = value.replace(rxhtmlTag, "<$1></$2>"); - - try { - for ( var i = 0, l = this.length; i < l; i++ ) { - // Remove element nodes and prevent memory leaks - if ( this[i].nodeType === 1 ) { - jQuery.cleanData( this[i].getElementsByTagName("*") ); - this[i].innerHTML = value; - } - } - - // If using innerHTML throws an exception, use the fallback method - } catch(e) { - this.empty().append( value ); - } - - } else if ( jQuery.isFunction( value ) ) { - this.each(function(i){ - var self = jQuery( this ); - - self.html( value.call(this, i, self.html()) ); - }); - - } else { - this.empty().append( value ); - } - - return this; - }, - - replaceWith: function( value ) { - if ( this[0] && this[0].parentNode ) { - // Make sure that the elements are removed from the DOM before they are inserted - // this can help fix replacing a parent with child elements - if ( jQuery.isFunction( value ) ) { - return this.each(function(i) { - var self = jQuery(this), old = self.html(); - self.replaceWith( value.call( this, i, old ) ); - }); - } - - if ( typeof value !== "string" ) { - value = jQuery( value ).detach(); - } - - return this.each(function() { - var next = this.nextSibling, - parent = this.parentNode; - - jQuery( this ).remove(); - - if ( next ) { - jQuery(next).before( value ); - } else { - jQuery(parent).append( value ); - } - }); - } else { - return this.length ? - this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) : - this; - } - }, - - detach: function( selector ) { - return this.remove( selector, true ); - }, - - domManip: function( args, table, callback ) { - var results, first, fragment, parent, - value = args[0], - scripts = []; - - // We can't cloneNode fragments that contain checked, in WebKit - if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) { - return this.each(function() { - jQuery(this).domManip( args, table, callback, true ); - }); - } - - if ( jQuery.isFunction(value) ) { - return this.each(function(i) { - var self = jQuery(this); - args[0] = value.call(this, i, table ? self.html() : undefined); - self.domManip( args, table, callback ); - }); - } - - if ( this[0] ) { - parent = value && value.parentNode; - - // If we're in a fragment, just use that instead of building a new one - if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) { - results = { fragment: parent }; - - } else { - results = jQuery.buildFragment( args, this, scripts ); - } - - fragment = results.fragment; - - if ( fragment.childNodes.length === 1 ) { - first = fragment = fragment.firstChild; - } else { - first = fragment.firstChild; - } - - if ( first ) { - table = table && jQuery.nodeName( first, "tr" ); - - for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) { - callback.call( - table ? - root(this[i], first) : - this[i], - // Make sure that we do not leak memory by inadvertently discarding - // the original fragment (which might have attached data) instead of - // using it; in addition, use the original fragment object for the last - // item instead of first because it can end up being emptied incorrectly - // in certain situations (Bug #8070). - // Fragments from the fragment cache must always be cloned and never used - // in place. - results.cacheable || (l > 1 && i < lastIndex) ? - jQuery.clone( fragment, true, true ) : - fragment - ); - } - } - - if ( scripts.length ) { - jQuery.each( scripts, evalScript ); - } - } - - return this; - } -}); - -function root( elem, cur ) { - return jQuery.nodeName(elem, "table") ? - (elem.getElementsByTagName("tbody")[0] || - elem.appendChild(elem.ownerDocument.createElement("tbody"))) : - elem; -} - -function cloneCopyEvent( src, dest ) { - - if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { - return; - } - - var internalKey = jQuery.expando, - oldData = jQuery.data( src ), - curData = jQuery.data( dest, oldData ); - - // Switch to use the internal data object, if it exists, for the next - // stage of data copying - if ( (oldData = oldData[ internalKey ]) ) { - var events = oldData.events; - curData = curData[ internalKey ] = jQuery.extend({}, oldData); - - if ( events ) { - delete curData.handle; - curData.events = {}; - - for ( var type in events ) { - for ( var i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type + ( events[ type ][ i ].namespace ? "." : "" ) + events[ type ][ i ].namespace, events[ type ][ i ], events[ type ][ i ].data ); - } - } - } - } -} - -function cloneFixAttributes( src, dest ) { - var nodeName; - - // We do not need to do anything for non-Elements - if ( dest.nodeType !== 1 ) { - return; - } - - // clearAttributes removes the attributes, which we don't want, - // but also removes the attachEvent events, which we *do* want - if ( dest.clearAttributes ) { - dest.clearAttributes(); - } - - // mergeAttributes, in contrast, only merges back on the - // original attributes, not the events - if ( dest.mergeAttributes ) { - dest.mergeAttributes( src ); - } - - nodeName = dest.nodeName.toLowerCase(); - - // IE6-8 fail to clone children inside object elements that use - // the proprietary classid attribute value (rather than the type - // attribute) to identify the type of content to display - if ( nodeName === "object" ) { - dest.outerHTML = src.outerHTML; - - } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) { - // IE6-8 fails to persist the checked state of a cloned checkbox - // or radio button. Worse, IE6-7 fail to give the cloned element - // a checked appearance if the defaultChecked value isn't also set - if ( src.checked ) { - dest.defaultChecked = dest.checked = src.checked; - } - - // IE6-7 get confused and end up setting the value of a cloned - // checkbox/radio button to an empty string instead of "on" - if ( dest.value !== src.value ) { - dest.value = src.value; - } - - // IE6-8 fails to return the selected option to the default selected - // state when cloning options - } else if ( nodeName === "option" ) { - dest.selected = src.defaultSelected; - - // IE6-8 fails to set the defaultValue to the correct value when - // cloning other types of input fields - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } - - // Event data gets referenced instead of copied if the expando - // gets copied too - dest.removeAttribute( jQuery.expando ); -} - -jQuery.buildFragment = function( args, nodes, scripts ) { - var fragment, cacheable, cacheresults, doc; - - // nodes may contain either an explicit document object, - // a jQuery collection or context object. - // If nodes[0] contains a valid object to assign to doc - if ( nodes && nodes[0] ) { - doc = nodes[0].ownerDocument || nodes[0]; - } - - // Ensure that an attr object doesn't incorrectly stand in as a document object - // Chrome and Firefox seem to allow this to occur and will throw exception - // Fixes #8950 - if ( !doc.createDocumentFragment ) { - doc = document; - } - - // Only cache "small" (1/2 KB) HTML strings that are associated with the main document - // Cloning options loses the selected state, so don't cache them - // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment - // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache - if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document && - args[0].charAt(0) === "<" && !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) { - - cacheable = true; - - cacheresults = jQuery.fragments[ args[0] ]; - if ( cacheresults && cacheresults !== 1 ) { - fragment = cacheresults; - } - } - - if ( !fragment ) { - fragment = doc.createDocumentFragment(); - jQuery.clean( args, doc, fragment, scripts ); - } - - if ( cacheable ) { - jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1; - } - - return { fragment: fragment, cacheable: cacheable }; -}; - -jQuery.fragments = {}; - -jQuery.each({ - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var ret = [], - insert = jQuery( selector ), - parent = this.length === 1 && this[0].parentNode; - - if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { - insert[ original ]( this[0] ); - return this; - - } else { - for ( var i = 0, l = insert.length; i < l; i++ ) { - var elems = (i > 0 ? this.clone(true) : this).get(); - jQuery( insert[i] )[ original ]( elems ); - ret = ret.concat( elems ); - } - - return this.pushStack( ret, name, insert.selector ); - } - }; -}); - -function getAll( elem ) { - if ( "getElementsByTagName" in elem ) { - return elem.getElementsByTagName( "*" ); - - } else if ( "querySelectorAll" in elem ) { - return elem.querySelectorAll( "*" ); - - } else { - return []; - } -} - -// Used in clean, fixes the defaultChecked property -function fixDefaultChecked( elem ) { - if ( elem.type === "checkbox" || elem.type === "radio" ) { - elem.defaultChecked = elem.checked; - } -} -// Finds all inputs and passes them to fixDefaultChecked -function findInputs( elem ) { - if ( jQuery.nodeName( elem, "input" ) ) { - fixDefaultChecked( elem ); - } else if ( "getElementsByTagName" in elem ) { - jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked ); - } -} - -jQuery.extend({ - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var clone = elem.cloneNode(true), - srcElements, - destElements, - i; - - if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && - (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { - // IE copies events bound via attachEvent when using cloneNode. - // Calling detachEvent on the clone will also remove the events - // from the original. In order to get around this, we use some - // proprietary methods to clear the events. Thanks to MooTools - // guys for this hotness. - - cloneFixAttributes( elem, clone ); - - // Using Sizzle here is crazy slow, so we use getElementsByTagName - // instead - srcElements = getAll( elem ); - destElements = getAll( clone ); - - // Weird iteration because IE will replace the length property - // with an element if you are cloning the body and one of the - // elements on the page has a name or id of "length" - for ( i = 0; srcElements[i]; ++i ) { - // Ensure that the destination node is not null; Fixes #9587 - if ( destElements[i] ) { - cloneFixAttributes( srcElements[i], destElements[i] ); - } - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - cloneCopyEvent( elem, clone ); - - if ( deepDataAndEvents ) { - srcElements = getAll( elem ); - destElements = getAll( clone ); - - for ( i = 0; srcElements[i]; ++i ) { - cloneCopyEvent( srcElements[i], destElements[i] ); - } - } - } - - srcElements = destElements = null; - - // Return the cloned set - return clone; - }, - - clean: function( elems, context, fragment, scripts ) { - var checkScriptType; - - context = context || document; - - // !context.createElement fails in IE with an error but returns typeof 'object' - if ( typeof context.createElement === "undefined" ) { - context = context.ownerDocument || context[0] && context[0].ownerDocument || document; - } - - var ret = [], j; - - for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { - if ( typeof elem === "number" ) { - elem += ""; - } - - if ( !elem ) { - continue; - } - - // Convert html string into DOM nodes - if ( typeof elem === "string" ) { - if ( !rhtml.test( elem ) ) { - elem = context.createTextNode( elem ); - } else { - // Fix "XHTML"-style tags in all browsers - elem = elem.replace(rxhtmlTag, "<$1></$2>"); - - // Trim whitespace, otherwise indexOf won't work as expected - var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(), - wrap = wrapMap[ tag ] || wrapMap._default, - depth = wrap[0], - div = context.createElement("div"); - - // Go to html and back, then peel off extra wrappers - div.innerHTML = wrap[1] + elem + wrap[2]; - - // Move to the right depth - while ( depth-- ) { - div = div.lastChild; - } - - // Remove IE's autoinserted <tbody> from table fragments - if ( !jQuery.support.tbody ) { - - // String was a <table>, *may* have spurious <tbody> - var hasBody = rtbody.test(elem), - tbody = tag === "table" && !hasBody ? - div.firstChild && div.firstChild.childNodes : - - // String was a bare <thead> or <tfoot> - wrap[1] === "<table>" && !hasBody ? - div.childNodes : - []; - - for ( j = tbody.length - 1; j >= 0 ; --j ) { - if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) { - tbody[ j ].parentNode.removeChild( tbody[ j ] ); - } - } - } - - // IE completely kills leading whitespace when innerHTML is used - if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { - div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild ); - } - - elem = div.childNodes; - } - } - - // Resets defaultChecked for any radios and checkboxes - // about to be appended to the DOM in IE 6/7 (#8060) - var len; - if ( !jQuery.support.appendChecked ) { - if ( elem[0] && typeof (len = elem.length) === "number" ) { - for ( j = 0; j < len; j++ ) { - findInputs( elem[j] ); - } - } else { - findInputs( elem ); - } - } - - if ( elem.nodeType ) { - ret.push( elem ); - } else { - ret = jQuery.merge( ret, elem ); - } - } - - if ( fragment ) { - checkScriptType = function( elem ) { - return !elem.type || rscriptType.test( elem.type ); - }; - for ( i = 0; ret[i]; i++ ) { - if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) { - scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] ); - - } else { - if ( ret[i].nodeType === 1 ) { - var jsTags = jQuery.grep( ret[i].getElementsByTagName( "script" ), checkScriptType ); - - ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) ); - } - fragment.appendChild( ret[i] ); - } - } - } - - return ret; - }, - - cleanData: function( elems ) { - var data, id, cache = jQuery.cache, internalKey = jQuery.expando, special = jQuery.event.special, - deleteExpando = jQuery.support.deleteExpando; - - for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { - if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { - continue; - } - - id = elem[ jQuery.expando ]; - - if ( id ) { - data = cache[ id ] && cache[ id ][ internalKey ]; - - if ( data && data.events ) { - for ( var type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - - // Null the DOM reference to avoid IE6/7/8 leak (#7054) - if ( data.handle ) { - data.handle.elem = null; - } - } - - if ( deleteExpando ) { - delete elem[ jQuery.expando ]; - - } else if ( elem.removeAttribute ) { - elem.removeAttribute( jQuery.expando ); - } - - delete cache[ id ]; - } - } - } -}); - -function evalScript( i, elem ) { - if ( elem.src ) { - jQuery.ajax({ - url: elem.src, - async: false, - dataType: "script" - }); - } else { - jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "/*$0*/" ) ); - } - - if ( elem.parentNode ) { - elem.parentNode.removeChild( elem ); - } -} - - - - -var ralpha = /alpha\([^)]*\)/i, - ropacity = /opacity=([^)]*)/, - // fixed for IE9, see #8346 - rupper = /([A-Z]|^ms)/g, - rnumpx = /^-?\d+(?:px)?$/i, - rnum = /^-?\d/, - rrelNum = /^([\-+])=([\-+.\de]+)/, - - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssWidth = [ "Left", "Right" ], - cssHeight = [ "Top", "Bottom" ], - curCSS, - - getComputedStyle, - currentStyle; - -jQuery.fn.css = function( name, value ) { - // Setting 'undefined' is a no-op - if ( arguments.length === 2 && value === undefined ) { - return this; - } - - return jQuery.access( this, name, value, true, function( elem, name, value ) { - return value !== undefined ? - jQuery.style( elem, name, value ) : - jQuery.css( elem, name ); - }); -}; - -jQuery.extend({ - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - // We should always get a number back from opacity - var ret = curCSS( elem, "opacity", "opacity" ); - return ret === "" ? "1" : ret; - - } else { - return elem.style.opacity; - } - } - } - }, - - // Exclude the following css properties to add px - cssNumber: { - "fillOpacity": true, - "fontWeight": true, - "lineHeight": true, - "opacity": true, - "orphans": true, - "widows": true, - "zIndex": true, - "zoom": true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: { - // normalize float css property - "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" - }, - - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; - } - - // Make sure that we're working with the right name - var ret, type, origName = jQuery.camelCase( name ), - style = elem.style, hooks = jQuery.cssHooks[ origName ]; - - name = jQuery.cssProps[ origName ] || origName; - - // Check if we're setting a value - if ( value !== undefined ) { - type = typeof value; - - // convert relative number strings (+= or -=) to relative numbers. #7345 - if ( type === "string" && (ret = rrelNum.exec( value )) ) { - value = ( +( ret[1] + 1) * +ret[2] ) + parseFloat( jQuery.css( elem, name ) ); - // Fixes bug #9237 - type = "number"; - } - - // Make sure that NaN and null values aren't set. See: #7116 - if ( value == null || type === "number" && isNaN( value ) ) { - return; - } - - // If a number was passed in, add 'px' to the (except for certain CSS properties) - if ( type === "number" && !jQuery.cssNumber[ origName ] ) { - value += "px"; - } - - // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) { - // Wrapped to prevent IE from throwing errors when 'invalid' values are provided - // Fixes bug #5509 - try { - style[ name ] = value; - } catch(e) {} - } - - } else { - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { - return ret; - } - - // Otherwise just get the value from the style object - return style[ name ]; - } - }, - - css: function( elem, name, extra ) { - var ret, hooks; - - // Make sure that we're working with the right name - name = jQuery.camelCase( name ); - hooks = jQuery.cssHooks[ name ]; - name = jQuery.cssProps[ name ] || name; - - // cssFloat needs a special treatment - if ( name === "cssFloat" ) { - name = "float"; - } - - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) { - return ret; - - // Otherwise, if a way to get the computed value exists, use that - } else if ( curCSS ) { - return curCSS( elem, name ); - } - }, - - // A method for quickly swapping in/out CSS properties to get correct calculations - swap: function( elem, options, callback ) { - var old = {}; - - // Remember the old values, and insert the new ones - for ( var name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - callback.call( elem ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - } -}); - -// DEPRECATED, Use jQuery.css() instead -jQuery.curCSS = jQuery.css; - -jQuery.each(["height", "width"], function( i, name ) { - jQuery.cssHooks[ name ] = { - get: function( elem, computed, extra ) { - var val; - - if ( computed ) { - if ( elem.offsetWidth !== 0 ) { - return getWH( elem, name, extra ); - } else { - jQuery.swap( elem, cssShow, function() { - val = getWH( elem, name, extra ); - }); - } - - return val; - } - }, - - set: function( elem, value ) { - if ( rnumpx.test( value ) ) { - // ignore negative width and height values #1599 - value = parseFloat( value ); - - if ( value >= 0 ) { - return value + "px"; - } - - } else { - return value; - } - } - }; -}); - -if ( !jQuery.support.opacity ) { - jQuery.cssHooks.opacity = { - get: function( elem, computed ) { - // IE uses filters for opacity - return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ? - ( parseFloat( RegExp.$1 ) / 100 ) + "" : - computed ? "1" : ""; - }, - - set: function( elem, value ) { - var style = elem.style, - currentStyle = elem.currentStyle, - opacity = jQuery.isNaN( value ) ? "" : "alpha(opacity=" + value * 100 + ")", - filter = currentStyle && currentStyle.filter || style.filter || ""; - - // IE has trouble with opacity if it does not have layout - // Force it by setting the zoom level - style.zoom = 1; - - // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652 - if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" ) { - - // Setting style.filter to null, "" & " " still leave "filter:" in the cssText - // if "filter:" is present at all, clearType is disabled, we want to avoid this - // style.removeAttribute is IE Only, but so apparently is this code path... - style.removeAttribute( "filter" ); - - // if there there is no filter style applied in a css rule, we are done - if ( currentStyle && !currentStyle.filter ) { - return; - } - } - - // otherwise, set new filter values - style.filter = ralpha.test( filter ) ? - filter.replace( ralpha, opacity ) : - filter + " " + opacity; - } - }; -} - -jQuery(function() { - // This hook cannot be added until DOM ready because the support test - // for it is not run until after DOM ready - if ( !jQuery.support.reliableMarginRight ) { - jQuery.cssHooks.marginRight = { - get: function( elem, computed ) { - // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right - // Work around by temporarily setting element display to inline-block - var ret; - jQuery.swap( elem, { "display": "inline-block" }, function() { - if ( computed ) { - ret = curCSS( elem, "margin-right", "marginRight" ); - } else { - ret = elem.style.marginRight; - } - }); - return ret; - } - }; - } -}); - -if ( document.defaultView && document.defaultView.getComputedStyle ) { - getComputedStyle = function( elem, name ) { - var ret, defaultView, computedStyle; - - name = name.replace( rupper, "-$1" ).toLowerCase(); - - if ( !(defaultView = elem.ownerDocument.defaultView) ) { - return undefined; - } - - if ( (computedStyle = defaultView.getComputedStyle( elem, null )) ) { - ret = computedStyle.getPropertyValue( name ); - if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) { - ret = jQuery.style( elem, name ); - } - } - - return ret; - }; -} - -if ( document.documentElement.currentStyle ) { - currentStyle = function( elem, name ) { - var left, - ret = elem.currentStyle && elem.currentStyle[ name ], - rsLeft = elem.runtimeStyle && elem.runtimeStyle[ name ], - style = elem.style; - - // From the awesome hack by Dean Edwards - // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 - - // If we're not dealing with a regular pixel number - // but a number that has a weird ending, we need to convert it to pixels - if ( !rnumpx.test( ret ) && rnum.test( ret ) ) { - // Remember the original values - left = style.left; - - // Put in the new values to get a computed value out - if ( rsLeft ) { - elem.runtimeStyle.left = elem.currentStyle.left; - } - style.left = name === "fontSize" ? "1em" : (ret || 0); - ret = style.pixelLeft + "px"; - - // Revert the changed values - style.left = left; - if ( rsLeft ) { - elem.runtimeStyle.left = rsLeft; - } - } - - return ret === "" ? "auto" : ret; - }; -} - -curCSS = getComputedStyle || currentStyle; - -function getWH( elem, name, extra ) { - - // Start with offset property - var val = name === "width" ? elem.offsetWidth : elem.offsetHeight, - which = name === "width" ? cssWidth : cssHeight; - - if ( val > 0 ) { - if ( extra !== "border" ) { - jQuery.each( which, function() { - if ( !extra ) { - val -= parseFloat( jQuery.css( elem, "padding" + this ) ) || 0; - } - if ( extra === "margin" ) { - val += parseFloat( jQuery.css( elem, extra + this ) ) || 0; - } else { - val -= parseFloat( jQuery.css( elem, "border" + this + "Width" ) ) || 0; - } - }); - } - - return val + "px"; - } - - // Fall back to computed then uncomputed css if necessary - val = curCSS( elem, name, name ); - if ( val < 0 || val == null ) { - val = elem.style[ name ] || 0; - } - // Normalize "", auto, and prepare for extra - val = parseFloat( val ) || 0; - - // Add padding, border, margin - if ( extra ) { - jQuery.each( which, function() { - val += parseFloat( jQuery.css( elem, "padding" + this ) ) || 0; - if ( extra !== "padding" ) { - val += parseFloat( jQuery.css( elem, "border" + this + "Width" ) ) || 0; - } - if ( extra === "margin" ) { - val += parseFloat( jQuery.css( elem, extra + this ) ) || 0; - } - }); - } - - return val + "px"; -} - -if ( jQuery.expr && jQuery.expr.filters ) { - jQuery.expr.filters.hidden = function( elem ) { - var width = elem.offsetWidth, - height = elem.offsetHeight; - - return (width === 0 && height === 0) || (!jQuery.support.reliableHiddenOffsets && (elem.style.display || jQuery.css( elem, "display" )) === "none"); - }; - - jQuery.expr.filters.visible = function( elem ) { - return !jQuery.expr.filters.hidden( elem ); - }; -} - - - - -var r20 = /%20/g, - rbracket = /\[\]$/, - rCRLF = /\r?\n/g, - rhash = /#.*$/, - rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL - rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, - // #7653, #8125, #8152: local protocol detection - rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/, - rnoContent = /^(?:GET|HEAD)$/, - rprotocol = /^\/\//, - rquery = /\?/, - rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, - rselectTextarea = /^(?:select|textarea)/i, - rspacesAjax = /\s+/, - rts = /([?&])_=[^&]*/, - rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/, - - // Keep a copy of the old load method - _load = jQuery.fn.load, - - /* Prefilters - * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) - * 2) These are called: - * - BEFORE asking for a transport - * - AFTER param serialization (s.data is a string if s.processData is true) - * 3) key is the dataType - * 4) the catchall symbol "*" can be used - * 5) execution will start with transport dataType and THEN continue down to "*" if needed - */ - prefilters = {}, - - /* Transports bindings - * 1) key is the dataType - * 2) the catchall symbol "*" can be used - * 3) selection will start with transport dataType and THEN go to "*" if needed - */ - transports = {}, - - // Document location - ajaxLocation, - - // Document location segments - ajaxLocParts, - - // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression - allTypes = ["*/"] + ["*"]; - -// #8138, IE may throw an exception when accessing -// a field from window.location if document.domain has been set -try { - ajaxLocation = location.href; -} catch( e ) { - // Use the href attribute of an A element - // since IE will modify it given document.location - ajaxLocation = document.createElement( "a" ); - ajaxLocation.href = ""; - ajaxLocation = ajaxLocation.href; -} - -// Segment location into parts -ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || []; - -// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport -function addToPrefiltersOrTransports( structure ) { - - // dataTypeExpression is optional and defaults to "*" - return function( dataTypeExpression, func ) { - - if ( typeof dataTypeExpression !== "string" ) { - func = dataTypeExpression; - dataTypeExpression = "*"; - } - - if ( jQuery.isFunction( func ) ) { - var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ), - i = 0, - length = dataTypes.length, - dataType, - list, - placeBefore; - - // For each dataType in the dataTypeExpression - for(; i < length; i++ ) { - dataType = dataTypes[ i ]; - // We control if we're asked to add before - // any existing element - placeBefore = /^\+/.test( dataType ); - if ( placeBefore ) { - dataType = dataType.substr( 1 ) || "*"; - } - list = structure[ dataType ] = structure[ dataType ] || []; - // then we add to the structure accordingly - list[ placeBefore ? "unshift" : "push" ]( func ); - } - } - }; -} - -// Base inspection function for prefilters and transports -function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR, - dataType /* internal */, inspected /* internal */ ) { - - dataType = dataType || options.dataTypes[ 0 ]; - inspected = inspected || {}; - - inspected[ dataType ] = true; - - var list = structure[ dataType ], - i = 0, - length = list ? list.length : 0, - executeOnly = ( structure === prefilters ), - selection; - - for(; i < length && ( executeOnly || !selection ); i++ ) { - selection = list[ i ]( options, originalOptions, jqXHR ); - // If we got redirected to another dataType - // we try there if executing only and not done already - if ( typeof selection === "string" ) { - if ( !executeOnly || inspected[ selection ] ) { - selection = undefined; - } else { - options.dataTypes.unshift( selection ); - selection = inspectPrefiltersOrTransports( - structure, options, originalOptions, jqXHR, selection, inspected ); - } - } - } - // If we're only executing or nothing was selected - // we try the catchall dataType if not done already - if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) { - selection = inspectPrefiltersOrTransports( - structure, options, originalOptions, jqXHR, "*", inspected ); - } - // unnecessary when only executing (prefilters) - // but it'll be ignored by the caller in that case - return selection; -} - -// A special extend for ajax options -// that takes "flat" options (not to be deep extended) -// Fixes #9887 -function ajaxExtend( target, src ) { - var key, deep, - flatOptions = jQuery.ajaxSettings.flatOptions || {}; - for( key in src ) { - if ( src[ key ] !== undefined ) { - ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; - } - } - if ( deep ) { - jQuery.extend( true, target, deep ); - } -} - -jQuery.fn.extend({ - load: function( url, params, callback ) { - if ( typeof url !== "string" && _load ) { - return _load.apply( this, arguments ); - - // Don't do a request if no elements are being requested - } else if ( !this.length ) { - return this; - } - - var off = url.indexOf( " " ); - if ( off >= 0 ) { - var selector = url.slice( off, url.length ); - url = url.slice( 0, off ); - } - - // Default to a GET request - var type = "GET"; - - // If the second parameter was provided - if ( params ) { - // If it's a function - if ( jQuery.isFunction( params ) ) { - // We assume that it's the callback - callback = params; - params = undefined; - - // Otherwise, build a param string - } else if ( typeof params === "object" ) { - params = jQuery.param( params, jQuery.ajaxSettings.traditional ); - type = "POST"; - } - } - - var self = this; - - // Request the remote document - jQuery.ajax({ - url: url, - type: type, - dataType: "html", - data: params, - // Complete callback (responseText is used internally) - complete: function( jqXHR, status, responseText ) { - // Store the response as specified by the jqXHR object - responseText = jqXHR.responseText; - // If successful, inject the HTML into all the matched elements - if ( jqXHR.isResolved() ) { - // #4825: Get the actual response in case - // a dataFilter is present in ajaxSettings - jqXHR.done(function( r ) { - responseText = r; - }); - // See if a selector was specified - self.html( selector ? - // Create a dummy div to hold the results - jQuery("<div>") - // inject the contents of the document in, removing the scripts - // to avoid any 'Permission Denied' errors in IE - .append(responseText.replace(rscript, "")) - - // Locate the specified elements - .find(selector) : - - // If not, just inject the full result - responseText ); - } - - if ( callback ) { - self.each( callback, [ responseText, status, jqXHR ] ); - } - } - }); - - return this; - }, - - serialize: function() { - return jQuery.param( this.serializeArray() ); - }, - - serializeArray: function() { - return this.map(function(){ - return this.elements ? jQuery.makeArray( this.elements ) : this; - }) - .filter(function(){ - return this.name && !this.disabled && - ( this.checked || rselectTextarea.test( this.nodeName ) || - rinput.test( this.type ) ); - }) - .map(function( i, elem ){ - var val = jQuery( this ).val(); - - return val == null ? - null : - jQuery.isArray( val ) ? - jQuery.map( val, function( val, i ){ - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - }) : - { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - }).get(); - } -}); - -// Attach a bunch of functions for handling common AJAX events -jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){ - jQuery.fn[ o ] = function( f ){ - return this.bind( o, f ); - }; -}); - -jQuery.each( [ "get", "post" ], function( i, method ) { - jQuery[ method ] = function( url, data, callback, type ) { - // shift arguments if data argument was omitted - if ( jQuery.isFunction( data ) ) { - type = type || callback; - callback = data; - data = undefined; - } - - return jQuery.ajax({ - type: method, - url: url, - data: data, - success: callback, - dataType: type - }); - }; -}); - -jQuery.extend({ - - getScript: function( url, callback ) { - return jQuery.get( url, undefined, callback, "script" ); - }, - - getJSON: function( url, data, callback ) { - return jQuery.get( url, data, callback, "json" ); - }, - - // Creates a full fledged settings object into target - // with both ajaxSettings and settings fields. - // If target is omitted, writes into ajaxSettings. - ajaxSetup: function( target, settings ) { - if ( settings ) { - // Building a settings object - ajaxExtend( target, jQuery.ajaxSettings ); - } else { - // Extending ajaxSettings - settings = target; - target = jQuery.ajaxSettings; - } - ajaxExtend( target, settings ); - return target; - }, - - ajaxSettings: { - url: ajaxLocation, - isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ), - global: true, - type: "GET", - contentType: "application/x-www-form-urlencoded", - processData: true, - async: true, - /* - timeout: 0, - data: null, - dataType: null, - username: null, - password: null, - cache: null, - traditional: false, - headers: {}, - */ - - accepts: { - xml: "application/xml, text/xml", - html: "text/html", - text: "text/plain", - json: "application/json, text/javascript", - "*": allTypes - }, - - contents: { - xml: /xml/, - html: /html/, - json: /json/ - }, - - responseFields: { - xml: "responseXML", - text: "responseText" - }, - - // List of data converters - // 1) key format is "source_type destination_type" (a single space in-between) - // 2) the catchall symbol "*" can be used for source_type - converters: { - - // Convert anything to text - "* text": window.String, - - // Text to html (true = no transformation) - "text html": true, - - // Evaluate text as a json expression - "text json": jQuery.parseJSON, - - // Parse text as xml - "text xml": jQuery.parseXML - }, - - // For options that shouldn't be deep extended: - // you can add your own custom options here if - // and when you create one that shouldn't be - // deep extended (see ajaxExtend) - flatOptions: { - context: true, - url: true - } - }, - - ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), - ajaxTransport: addToPrefiltersOrTransports( transports ), - - // Main method - ajax: function( url, options ) { - - // If url is an object, simulate pre-1.5 signature - if ( typeof url === "object" ) { - options = url; - url = undefined; - } - - // Force options to be an object - options = options || {}; - - var // Create the final options object - s = jQuery.ajaxSetup( {}, options ), - // Callbacks context - callbackContext = s.context || s, - // Context for global events - // It's the callbackContext if one was provided in the options - // and if it's a DOM node or a jQuery collection - globalEventContext = callbackContext !== s && - ( callbackContext.nodeType || callbackContext instanceof jQuery ) ? - jQuery( callbackContext ) : jQuery.event, - // Deferreds - deferred = jQuery.Deferred(), - completeDeferred = jQuery._Deferred(), - // Status-dependent callbacks - statusCode = s.statusCode || {}, - // ifModified key - ifModifiedKey, - // Headers (they are sent all at once) - requestHeaders = {}, - requestHeadersNames = {}, - // Response headers - responseHeadersString, - responseHeaders, - // transport - transport, - // timeout handle - timeoutTimer, - // Cross-domain detection vars - parts, - // The jqXHR state - state = 0, - // To know if global events are to be dispatched - fireGlobals, - // Loop variable - i, - // Fake xhr - jqXHR = { - - readyState: 0, - - // Caches the header - setRequestHeader: function( name, value ) { - if ( !state ) { - var lname = name.toLowerCase(); - name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name; - requestHeaders[ name ] = value; - } - return this; - }, - - // Raw string - getAllResponseHeaders: function() { - return state === 2 ? responseHeadersString : null; - }, - - // Builds headers hashtable if needed - getResponseHeader: function( key ) { - var match; - if ( state === 2 ) { - if ( !responseHeaders ) { - responseHeaders = {}; - while( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[1].toLowerCase() ] = match[ 2 ]; - } - } - match = responseHeaders[ key.toLowerCase() ]; - } - return match === undefined ? null : match; - }, - - // Overrides response content-type header - overrideMimeType: function( type ) { - if ( !state ) { - s.mimeType = type; - } - return this; - }, - - // Cancel the request - abort: function( statusText ) { - statusText = statusText || "abort"; - if ( transport ) { - transport.abort( statusText ); - } - done( 0, statusText ); - return this; - } - }; - - // Callback for when everything is done - // It is defined here because jslint complains if it is declared - // at the end of the function (which would be more logical and readable) - function done( status, nativeStatusText, responses, headers ) { - - // Called once - if ( state === 2 ) { - return; - } - - // State is "done" now - state = 2; - - // Clear timeout if it exists - if ( timeoutTimer ) { - clearTimeout( timeoutTimer ); - } - - // Dereference transport for early garbage collection - // (no matter how long the jqXHR object will be used) - transport = undefined; - - // Cache response headers - responseHeadersString = headers || ""; - - // Set readyState - jqXHR.readyState = status > 0 ? 4 : 0; - - var isSuccess, - success, - error, - statusText = nativeStatusText, - response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined, - lastModified, - etag; - - // If successful, handle type chaining - if ( status >= 200 && status < 300 || status === 304 ) { - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - - if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) { - jQuery.lastModified[ ifModifiedKey ] = lastModified; - } - if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) { - jQuery.etag[ ifModifiedKey ] = etag; - } - } - - // If not modified - if ( status === 304 ) { - - statusText = "notmodified"; - isSuccess = true; - - // If we have data - } else { - - try { - success = ajaxConvert( s, response ); - statusText = "success"; - isSuccess = true; - } catch(e) { - // We have a parsererror - statusText = "parsererror"; - error = e; - } - } - } else { - // We extract error from statusText - // then normalize statusText and status for non-aborts - error = statusText; - if( !statusText || status ) { - statusText = "error"; - if ( status < 0 ) { - status = 0; - } - } - } - - // Set data for the fake xhr object - jqXHR.status = status; - jqXHR.statusText = "" + ( nativeStatusText || statusText ); - - // Success/Error - if ( isSuccess ) { - deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); - } else { - deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); - } - - // Status-dependent callbacks - jqXHR.statusCode( statusCode ); - statusCode = undefined; - - if ( fireGlobals ) { - globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ), - [ jqXHR, s, isSuccess ? success : error ] ); - } - - // Complete - completeDeferred.resolveWith( callbackContext, [ jqXHR, statusText ] ); - - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); - // Handle the global AJAX counter - if ( !( --jQuery.active ) ) { - jQuery.event.trigger( "ajaxStop" ); - } - } - } - - // Attach deferreds - deferred.promise( jqXHR ); - jqXHR.success = jqXHR.done; - jqXHR.error = jqXHR.fail; - jqXHR.complete = completeDeferred.done; - - // Status-dependent callbacks - jqXHR.statusCode = function( map ) { - if ( map ) { - var tmp; - if ( state < 2 ) { - for( tmp in map ) { - statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ]; - } - } else { - tmp = map[ jqXHR.status ]; - jqXHR.then( tmp, tmp ); - } - } - return this; - }; - - // Remove hash character (#7531: and string promotion) - // Add protocol if not provided (#5866: IE7 issue with protocol-less urls) - // We also use the url parameter if available - s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" ); - - // Extract dataTypes list - s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax ); - - // Determine if a cross-domain request is in order - if ( s.crossDomain == null ) { - parts = rurl.exec( s.url.toLowerCase() ); - s.crossDomain = !!( parts && - ( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] || - ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) != - ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) ) - ); - } - - // Convert data if not already a string - if ( s.data && s.processData && typeof s.data !== "string" ) { - s.data = jQuery.param( s.data, s.traditional ); - } - - // Apply prefilters - inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); - - // If request was aborted inside a prefiler, stop there - if ( state === 2 ) { - return false; - } - - // We can fire global events as of now if asked to - fireGlobals = s.global; - - // Uppercase the type - s.type = s.type.toUpperCase(); - - // Determine if request has content - s.hasContent = !rnoContent.test( s.type ); - - // Watch for a new set of requests - if ( fireGlobals && jQuery.active++ === 0 ) { - jQuery.event.trigger( "ajaxStart" ); - } - - // More options handling for requests with no content - if ( !s.hasContent ) { - - // If data is available, append data to url - if ( s.data ) { - s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; - // #9682: remove data so that it's not used in an eventual retry - delete s.data; - } - - // Get ifModifiedKey before adding the anti-cache parameter - ifModifiedKey = s.url; - - // Add anti-cache in url if needed - if ( s.cache === false ) { - - var ts = jQuery.now(), - // try replacing _= if it is there - ret = s.url.replace( rts, "$1_=" + ts ); - - // if nothing was replaced, add timestamp to the end - s.url = ret + ( (ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" ); - } - } - - // Set the correct header, if data is being sent - if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { - jqXHR.setRequestHeader( "Content-Type", s.contentType ); - } - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - ifModifiedKey = ifModifiedKey || s.url; - if ( jQuery.lastModified[ ifModifiedKey ] ) { - jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] ); - } - if ( jQuery.etag[ ifModifiedKey ] ) { - jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] ); - } - } - - // Set the Accepts header for the server, depending on the dataType - jqXHR.setRequestHeader( - "Accept", - s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ? - s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : - s.accepts[ "*" ] - ); - - // Check for headers option - for ( i in s.headers ) { - jqXHR.setRequestHeader( i, s.headers[ i ] ); - } - - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) { - // Abort if not done already - jqXHR.abort(); - return false; - - } - - // Install callbacks on deferreds - for ( i in { success: 1, error: 1, complete: 1 } ) { - jqXHR[ i ]( s[ i ] ); - } - - // Get transport - transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); - - // If no transport, we auto-abort - if ( !transport ) { - done( -1, "No Transport" ); - } else { - jqXHR.readyState = 1; - // Send global event - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); - } - // Timeout - if ( s.async && s.timeout > 0 ) { - timeoutTimer = setTimeout( function(){ - jqXHR.abort( "timeout" ); - }, s.timeout ); - } - - try { - state = 1; - transport.send( requestHeaders, done ); - } catch (e) { - // Propagate exception as error if not done - if ( state < 2 ) { - done( -1, e ); - // Simply rethrow otherwise - } else { - jQuery.error( e ); - } - } - } - - return jqXHR; - }, - - // Serialize an array of form elements or a set of - // key/values into a query string - param: function( a, traditional ) { - var s = [], - add = function( key, value ) { - // If value is a function, invoke it and return its value - value = jQuery.isFunction( value ) ? value() : value; - s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value ); - }; - - // Set traditional to true for jQuery <= 1.3.2 behavior. - if ( traditional === undefined ) { - traditional = jQuery.ajaxSettings.traditional; - } - - // If an array was passed in, assume that it is an array of form elements. - if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { - // Serialize the form elements - jQuery.each( a, function() { - add( this.name, this.value ); - }); - - } else { - // If traditional, encode the "old" way (the way 1.3.2 or older - // did it), otherwise encode params recursively. - for ( var prefix in a ) { - buildParams( prefix, a[ prefix ], traditional, add ); - } - } - - // Return the resulting serialization - return s.join( "&" ).replace( r20, "+" ); - } -}); - -function buildParams( prefix, obj, traditional, add ) { - if ( jQuery.isArray( obj ) ) { - // Serialize array item. - jQuery.each( obj, function( i, v ) { - if ( traditional || rbracket.test( prefix ) ) { - // Treat each array item as a scalar. - add( prefix, v ); - - } else { - // If array item is non-scalar (array or object), encode its - // numeric index to resolve deserialization ambiguity issues. - // Note that rack (as of 1.0.0) can't currently deserialize - // nested arrays properly, and attempting to do so may cause - // a server error. Possible fixes are to modify rack's - // deserialization algorithm or to provide an option or flag - // to force array serialization to be shallow. - buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add ); - } - }); - - } else if ( !traditional && obj != null && typeof obj === "object" ) { - // Serialize object item. - for ( var name in obj ) { - buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); - } - - } else { - // Serialize scalar item. - add( prefix, obj ); - } -} - -// This is still on the jQuery object... for now -// Want to move this to jQuery.ajax some day -jQuery.extend({ - - // Counter for holding the number of active queries - active: 0, - - // Last-Modified header cache for next request - lastModified: {}, - etag: {} - -}); - -/* Handles responses to an ajax request: - * - sets all responseXXX fields accordingly - * - finds the right dataType (mediates between content-type and expected dataType) - * - returns the corresponding response - */ -function ajaxHandleResponses( s, jqXHR, responses ) { - - var contents = s.contents, - dataTypes = s.dataTypes, - responseFields = s.responseFields, - ct, - type, - finalDataType, - firstDataType; - - // Fill responseXXX fields - for( type in responseFields ) { - if ( type in responses ) { - jqXHR[ responseFields[type] ] = responses[ type ]; - } - } - - // Remove auto dataType and get content-type in the process - while( dataTypes[ 0 ] === "*" ) { - dataTypes.shift(); - if ( ct === undefined ) { - ct = s.mimeType || jqXHR.getResponseHeader( "content-type" ); - } - } - - // Check if we're dealing with a known content-type - if ( ct ) { - for ( type in contents ) { - if ( contents[ type ] && contents[ type ].test( ct ) ) { - dataTypes.unshift( type ); - break; - } - } - } - - // Check to see if we have a response for the expected dataType - if ( dataTypes[ 0 ] in responses ) { - finalDataType = dataTypes[ 0 ]; - } else { - // Try convertible dataTypes - for ( type in responses ) { - if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) { - finalDataType = type; - break; - } - if ( !firstDataType ) { - firstDataType = type; - } - } - // Or just use first one - finalDataType = finalDataType || firstDataType; - } - - // If we found a dataType - // We add the dataType to the list if needed - // and return the corresponding response - if ( finalDataType ) { - if ( finalDataType !== dataTypes[ 0 ] ) { - dataTypes.unshift( finalDataType ); - } - return responses[ finalDataType ]; - } -} - -// Chain conversions given the request and the original response -function ajaxConvert( s, response ) { - - // Apply the dataFilter if provided - if ( s.dataFilter ) { - response = s.dataFilter( response, s.dataType ); - } - - var dataTypes = s.dataTypes, - converters = {}, - i, - key, - length = dataTypes.length, - tmp, - // Current and previous dataTypes - current = dataTypes[ 0 ], - prev, - // Conversion expression - conversion, - // Conversion function - conv, - // Conversion functions (transitive conversion) - conv1, - conv2; - - // For each dataType in the chain - for( i = 1; i < length; i++ ) { - - // Create converters map - // with lowercased keys - if ( i === 1 ) { - for( key in s.converters ) { - if( typeof key === "string" ) { - converters[ key.toLowerCase() ] = s.converters[ key ]; - } - } - } - - // Get the dataTypes - prev = current; - current = dataTypes[ i ]; - - // If current is auto dataType, update it to prev - if( current === "*" ) { - current = prev; - // If no auto and dataTypes are actually different - } else if ( prev !== "*" && prev !== current ) { - - // Get the converter - conversion = prev + " " + current; - conv = converters[ conversion ] || converters[ "* " + current ]; - - // If there is no direct converter, search transitively - if ( !conv ) { - conv2 = undefined; - for( conv1 in converters ) { - tmp = conv1.split( " " ); - if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) { - conv2 = converters[ tmp[1] + " " + current ]; - if ( conv2 ) { - conv1 = converters[ conv1 ]; - if ( conv1 === true ) { - conv = conv2; - } else if ( conv2 === true ) { - conv = conv1; - } - break; - } - } - } - } - // If we found no converter, dispatch an error - if ( !( conv || conv2 ) ) { - jQuery.error( "No conversion from " + conversion.replace(" "," to ") ); - } - // If found converter is not an equivalence - if ( conv !== true ) { - // Convert with 1 or 2 converters accordingly - response = conv ? conv( response ) : conv2( conv1(response) ); - } - } - } - return response; -} - - - - -var jsc = jQuery.now(), - jsre = /(\=)\?(&|$)|\?\?/i; - -// Default jsonp settings -jQuery.ajaxSetup({ - jsonp: "callback", - jsonpCallback: function() { - return jQuery.expando + "_" + ( jsc++ ); - } -}); - -// Detect, normalize options and install callbacks for jsonp requests -jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { - - var inspectData = s.contentType === "application/x-www-form-urlencoded" && - ( typeof s.data === "string" ); - - if ( s.dataTypes[ 0 ] === "jsonp" || - s.jsonp !== false && ( jsre.test( s.url ) || - inspectData && jsre.test( s.data ) ) ) { - - var responseContainer, - jsonpCallback = s.jsonpCallback = - jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback, - previous = window[ jsonpCallback ], - url = s.url, - data = s.data, - replace = "$1" + jsonpCallback + "$2"; - - if ( s.jsonp !== false ) { - url = url.replace( jsre, replace ); - if ( s.url === url ) { - if ( inspectData ) { - data = data.replace( jsre, replace ); - } - if ( s.data === data ) { - // Add callback manually - url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback; - } - } - } - - s.url = url; - s.data = data; - - // Install callback - window[ jsonpCallback ] = function( response ) { - responseContainer = [ response ]; - }; - - // Clean-up function - jqXHR.always(function() { - // Set callback back to previous value - window[ jsonpCallback ] = previous; - // Call if it was a function and we have a response - if ( responseContainer && jQuery.isFunction( previous ) ) { - window[ jsonpCallback ]( responseContainer[ 0 ] ); - } - }); - - // Use data converter to retrieve json after script execution - s.converters["script json"] = function() { - if ( !responseContainer ) { - jQuery.error( jsonpCallback + " was not called" ); - } - return responseContainer[ 0 ]; - }; - - // force json dataType - s.dataTypes[ 0 ] = "json"; - - // Delegate to script - return "script"; - } -}); - - - - -// Install script dataType -jQuery.ajaxSetup({ - accepts: { - script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript" - }, - contents: { - script: /javascript|ecmascript/ - }, - converters: { - "text script": function( text ) { - jQuery.globalEval( text ); - return text; - } - } -}); - -// Handle cache's special case and global -jQuery.ajaxPrefilter( "script", function( s ) { - if ( s.cache === undefined ) { - s.cache = false; - } - if ( s.crossDomain ) { - s.type = "GET"; - s.global = false; - } -}); - -// Bind script tag hack transport -jQuery.ajaxTransport( "script", function(s) { - - // This transport only deals with cross domain requests - if ( s.crossDomain ) { - - var script, - head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement; - - return { - - send: function( _, callback ) { - - script = document.createElement( "script" ); - - script.async = "async"; - - if ( s.scriptCharset ) { - script.charset = s.scriptCharset; - } - - script.src = s.url; - - // Attach handlers for all browsers - script.onload = script.onreadystatechange = function( _, isAbort ) { - - if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) { - - // Handle memory leak in IE - script.onload = script.onreadystatechange = null; - - // Remove the script - if ( head && script.parentNode ) { - head.removeChild( script ); - } - - // Dereference the script - script = undefined; - - // Callback if not abort - if ( !isAbort ) { - callback( 200, "success" ); - } - } - }; - // Use insertBefore instead of appendChild to circumvent an IE6 bug. - // This arises when a base node is used (#2709 and #4378). - head.insertBefore( script, head.firstChild ); - }, - - abort: function() { - if ( script ) { - script.onload( 0, 1 ); - } - } - }; - } -}); - - - - -var // #5280: Internet Explorer will keep connections alive if we don't abort on unload - xhrOnUnloadAbort = window.ActiveXObject ? function() { - // Abort all pending requests - for ( var key in xhrCallbacks ) { - xhrCallbacks[ key ]( 0, 1 ); - } - } : false, - xhrId = 0, - xhrCallbacks; - -// Functions to create xhrs -function createStandardXHR() { - try { - return new window.XMLHttpRequest(); - } catch( e ) {} -} - -function createActiveXHR() { - try { - return new window.ActiveXObject( "Microsoft.XMLHTTP" ); - } catch( e ) {} -} - -// Create the request object -// (This is still attached to ajaxSettings for backward compatibility) -jQuery.ajaxSettings.xhr = window.ActiveXObject ? - /* Microsoft failed to properly - * implement the XMLHttpRequest in IE7 (can't request local files), - * so we use the ActiveXObject when it is available - * Additionally XMLHttpRequest can be disabled in IE7/IE8 so - * we need a fallback. - */ - function() { - return !this.isLocal && createStandardXHR() || createActiveXHR(); - } : - // For all other browsers, use the standard XMLHttpRequest object - createStandardXHR; - -// Determine support properties -(function( xhr ) { - jQuery.extend( jQuery.support, { - ajax: !!xhr, - cors: !!xhr && ( "withCredentials" in xhr ) - }); -})( jQuery.ajaxSettings.xhr() ); - -// Create transport if the browser can provide an xhr -if ( jQuery.support.ajax ) { - - jQuery.ajaxTransport(function( s ) { - // Cross domain only allowed if supported through XMLHttpRequest - if ( !s.crossDomain || jQuery.support.cors ) { - - var callback; - - return { - send: function( headers, complete ) { - - // Get a new xhr - var xhr = s.xhr(), - handle, - i; - - // Open the socket - // Passing null username, generates a login popup on Opera (#2865) - if ( s.username ) { - xhr.open( s.type, s.url, s.async, s.username, s.password ); - } else { - xhr.open( s.type, s.url, s.async ); - } - - // Apply custom fields if provided - if ( s.xhrFields ) { - for ( i in s.xhrFields ) { - xhr[ i ] = s.xhrFields[ i ]; - } - } - - // Override mime type if needed - if ( s.mimeType && xhr.overrideMimeType ) { - xhr.overrideMimeType( s.mimeType ); - } - - // X-Requested-With header - // For cross-domain requests, seeing as conditions for a preflight are - // akin to a jigsaw puzzle, we simply never set it to be sure. - // (it can always be set on a per-request basis or even using ajaxSetup) - // For same-domain requests, won't change header if already provided. - if ( !s.crossDomain && !headers["X-Requested-With"] ) { - headers[ "X-Requested-With" ] = "XMLHttpRequest"; - } - - // Need an extra try/catch for cross domain requests in Firefox 3 - try { - for ( i in headers ) { - xhr.setRequestHeader( i, headers[ i ] ); - } - } catch( _ ) {} - - // Do send the request - // This may raise an exception which is actually - // handled in jQuery.ajax (so no try/catch here) - xhr.send( ( s.hasContent && s.data ) || null ); - - // Listener - callback = function( _, isAbort ) { - - var status, - statusText, - responseHeaders, - responses, - xml; - - // Firefox throws exceptions when accessing properties - // of an xhr when a network error occured - // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE) - try { - - // Was never called and is aborted or complete - if ( callback && ( isAbort || xhr.readyState === 4 ) ) { - - // Only called once - callback = undefined; - - // Do not keep as active anymore - if ( handle ) { - xhr.onreadystatechange = jQuery.noop; - if ( xhrOnUnloadAbort ) { - delete xhrCallbacks[ handle ]; - } - } - - // If it's an abort - if ( isAbort ) { - // Abort it manually if needed - if ( xhr.readyState !== 4 ) { - xhr.abort(); - } - } else { - status = xhr.status; - responseHeaders = xhr.getAllResponseHeaders(); - responses = {}; - xml = xhr.responseXML; - - // Construct response list - if ( xml && xml.documentElement /* #4958 */ ) { - responses.xml = xml; - } - responses.text = xhr.responseText; - - // Firefox throws an exception when accessing - // statusText for faulty cross-domain requests - try { - statusText = xhr.statusText; - } catch( e ) { - // We normalize with Webkit giving an empty statusText - statusText = ""; - } - - // Filter status for non standard behaviors - - // If the request is local and we have data: assume a success - // (success with no data won't get notified, that's the best we - // can do given current implementations) - if ( !status && s.isLocal && !s.crossDomain ) { - status = responses.text ? 200 : 404; - // IE - #1450: sometimes returns 1223 when it should be 204 - } else if ( status === 1223 ) { - status = 204; - } - } - } - } catch( firefoxAccessException ) { - if ( !isAbort ) { - complete( -1, firefoxAccessException ); - } - } - - // Call complete if needed - if ( responses ) { - complete( status, statusText, responses, responseHeaders ); - } - }; - - // if we're in sync mode or it's in cache - // and has been retrieved directly (IE6 & IE7) - // we need to manually fire the callback - if ( !s.async || xhr.readyState === 4 ) { - callback(); - } else { - handle = ++xhrId; - if ( xhrOnUnloadAbort ) { - // Create the active xhrs callbacks list if needed - // and attach the unload handler - if ( !xhrCallbacks ) { - xhrCallbacks = {}; - jQuery( window ).unload( xhrOnUnloadAbort ); - } - // Add to list of active xhrs callbacks - xhrCallbacks[ handle ] = callback; - } - xhr.onreadystatechange = callback; - } - }, - - abort: function() { - if ( callback ) { - callback(0,1); - } - } - }; - } - }); -} - - - - -var elemdisplay = {}, - iframe, iframeDoc, - rfxtypes = /^(?:toggle|show|hide)$/, - rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i, - timerId, - fxAttrs = [ - // height animations - [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], - // width animations - [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], - // opacity animations - [ "opacity" ] - ], - fxNow; - -jQuery.fn.extend({ - show: function( speed, easing, callback ) { - var elem, display; - - if ( speed || speed === 0 ) { - return this.animate( genFx("show", 3), speed, easing, callback); - - } else { - for ( var i = 0, j = this.length; i < j; i++ ) { - elem = this[i]; - - if ( elem.style ) { - display = elem.style.display; - - // Reset the inline display of this element to learn if it is - // being hidden by cascaded rules or not - if ( !jQuery._data(elem, "olddisplay") && display === "none" ) { - display = elem.style.display = ""; - } - - // Set elements which have been overridden with display: none - // in a stylesheet to whatever the default browser style is - // for such an element - if ( display === "" && jQuery.css( elem, "display" ) === "none" ) { - jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName)); - } - } - } - - // Set the display of most of the elements in a second loop - // to avoid the constant reflow - for ( i = 0; i < j; i++ ) { - elem = this[i]; - - if ( elem.style ) { - display = elem.style.display; - - if ( display === "" || display === "none" ) { - elem.style.display = jQuery._data(elem, "olddisplay") || ""; - } - } - } - - return this; - } - }, - - hide: function( speed, easing, callback ) { - if ( speed || speed === 0 ) { - return this.animate( genFx("hide", 3), speed, easing, callback); - - } else { - for ( var i = 0, j = this.length; i < j; i++ ) { - if ( this[i].style ) { - var display = jQuery.css( this[i], "display" ); - - if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) { - jQuery._data( this[i], "olddisplay", display ); - } - } - } - - // Set the display of the elements in a second loop - // to avoid the constant reflow - for ( i = 0; i < j; i++ ) { - if ( this[i].style ) { - this[i].style.display = "none"; - } - } - - return this; - } - }, - - // Save the old toggle function - _toggle: jQuery.fn.toggle, - - toggle: function( fn, fn2, callback ) { - var bool = typeof fn === "boolean"; - - if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) { - this._toggle.apply( this, arguments ); - - } else if ( fn == null || bool ) { - this.each(function() { - var state = bool ? fn : jQuery(this).is(":hidden"); - jQuery(this)[ state ? "show" : "hide" ](); - }); - - } else { - this.animate(genFx("toggle", 3), fn, fn2, callback); - } - - return this; - }, - - fadeTo: function( speed, to, easing, callback ) { - return this.filter(":hidden").css("opacity", 0).show().end() - .animate({opacity: to}, speed, easing, callback); - }, - - animate: function( prop, speed, easing, callback ) { - var optall = jQuery.speed(speed, easing, callback); - - if ( jQuery.isEmptyObject( prop ) ) { - return this.each( optall.complete, [ false ] ); - } - - // Do not change referenced properties as per-property easing will be lost - prop = jQuery.extend( {}, prop ); - - return this[ optall.queue === false ? "each" : "queue" ](function() { - // XXX 'this' does not always have a nodeName when running the - // test suite - - if ( optall.queue === false ) { - jQuery._mark( this ); - } - - var opt = jQuery.extend( {}, optall ), - isElement = this.nodeType === 1, - hidden = isElement && jQuery(this).is(":hidden"), - name, val, p, - display, e, - parts, start, end, unit; - - // will store per property easing and be used to determine when an animation is complete - opt.animatedProperties = {}; - - for ( p in prop ) { - - // property name normalization - name = jQuery.camelCase( p ); - if ( p !== name ) { - prop[ name ] = prop[ p ]; - delete prop[ p ]; - } - - val = prop[ name ]; - - // easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default) - if ( jQuery.isArray( val ) ) { - opt.animatedProperties[ name ] = val[ 1 ]; - val = prop[ name ] = val[ 0 ]; - } else { - opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing'; - } - - if ( val === "hide" && hidden || val === "show" && !hidden ) { - return opt.complete.call( this ); - } - - if ( isElement && ( name === "height" || name === "width" ) ) { - // Make sure that nothing sneaks out - // Record all 3 overflow attributes because IE does not - // change the overflow attribute when overflowX and - // overflowY are set to the same value - opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ]; - - // Set display property to inline-block for height/width - // animations on inline elements that are having width/height - // animated - if ( jQuery.css( this, "display" ) === "inline" && - jQuery.css( this, "float" ) === "none" ) { - if ( !jQuery.support.inlineBlockNeedsLayout ) { - this.style.display = "inline-block"; - - } else { - display = defaultDisplay( this.nodeName ); - - // inline-level elements accept inline-block; - // block-level elements need to be inline with layout - if ( display === "inline" ) { - this.style.display = "inline-block"; - - } else { - this.style.display = "inline"; - this.style.zoom = 1; - } - } - } - } - } - - if ( opt.overflow != null ) { - this.style.overflow = "hidden"; - } - - for ( p in prop ) { - e = new jQuery.fx( this, opt, p ); - val = prop[ p ]; - - if ( rfxtypes.test(val) ) { - e[ val === "toggle" ? hidden ? "show" : "hide" : val ](); - - } else { - parts = rfxnum.exec( val ); - start = e.cur(); - - if ( parts ) { - end = parseFloat( parts[2] ); - unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" ); - - // We need to compute starting value - if ( unit !== "px" ) { - jQuery.style( this, p, (end || 1) + unit); - start = ((end || 1) / e.cur()) * start; - jQuery.style( this, p, start + unit); - } - - // If a +=/-= token was provided, we're doing a relative animation - if ( parts[1] ) { - end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start; - } - - e.custom( start, end, unit ); - - } else { - e.custom( start, val, "" ); - } - } - } - - // For JS strict compliance - return true; - }); - }, - - stop: function( clearQueue, gotoEnd ) { - if ( clearQueue ) { - this.queue([]); - } - - this.each(function() { - var timers = jQuery.timers, - i = timers.length; - // clear marker counters if we know they won't be - if ( !gotoEnd ) { - jQuery._unmark( true, this ); - } - while ( i-- ) { - if ( timers[i].elem === this ) { - if (gotoEnd) { - // force the next step to be the last - timers[i](true); - } - - timers.splice(i, 1); - } - } - }); - - // start the next in the queue if the last step wasn't forced - if ( !gotoEnd ) { - this.dequeue(); - } - - return this; - } - -}); - -// Animations created synchronously will run synchronously -function createFxNow() { - setTimeout( clearFxNow, 0 ); - return ( fxNow = jQuery.now() ); -} - -function clearFxNow() { - fxNow = undefined; -} - -// Generate parameters to create a standard animation -function genFx( type, num ) { - var obj = {}; - - jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() { - obj[ this ] = type; - }); - - return obj; -} - -// Generate shortcuts for custom animations -jQuery.each({ - slideDown: genFx("show", 1), - slideUp: genFx("hide", 1), - slideToggle: genFx("toggle", 1), - fadeIn: { opacity: "show" }, - fadeOut: { opacity: "hide" }, - fadeToggle: { opacity: "toggle" } -}, function( name, props ) { - jQuery.fn[ name ] = function( speed, easing, callback ) { - return this.animate( props, speed, easing, callback ); - }; -}); - -jQuery.extend({ - speed: function( speed, easing, fn ) { - var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) : { - complete: fn || !fn && easing || - jQuery.isFunction( speed ) && speed, - duration: speed, - easing: fn && easing || easing && !jQuery.isFunction(easing) && easing - }; - - opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : - opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default; - - // Queueing - opt.old = opt.complete; - opt.complete = function( noUnmark ) { - if ( jQuery.isFunction( opt.old ) ) { - opt.old.call( this ); - } - - if ( opt.queue !== false ) { - jQuery.dequeue( this ); - } else if ( noUnmark !== false ) { - jQuery._unmark( this ); - } - }; - - return opt; - }, - - easing: { - linear: function( p, n, firstNum, diff ) { - return firstNum + diff * p; - }, - swing: function( p, n, firstNum, diff ) { - return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum; - } - }, - - timers: [], - - fx: function( elem, options, prop ) { - this.options = options; - this.elem = elem; - this.prop = prop; - - options.orig = options.orig || {}; - } - -}); - -jQuery.fx.prototype = { - // Simple function for setting a style value - update: function() { - if ( this.options.step ) { - this.options.step.call( this.elem, this.now, this ); - } - - (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this ); - }, - - // Get the current size - cur: function() { - if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) { - return this.elem[ this.prop ]; - } - - var parsed, - r = jQuery.css( this.elem, this.prop ); - // Empty strings, null, undefined and "auto" are converted to 0, - // complex values such as "rotate(1rad)" are returned as is, - // simple values such as "10px" are parsed to Float. - return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed; - }, - - // Start an animation from one number to another - custom: function( from, to, unit ) { - var self = this, - fx = jQuery.fx; - - this.startTime = fxNow || createFxNow(); - this.start = from; - this.end = to; - this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" ); - this.now = this.start; - this.pos = this.state = 0; - - function t( gotoEnd ) { - return self.step(gotoEnd); - } - - t.elem = this.elem; - - if ( t() && jQuery.timers.push(t) && !timerId ) { - timerId = setInterval( fx.tick, fx.interval ); - } - }, - - // Simple 'show' function - show: function() { - // Remember where we started, so that we can go back to it later - this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); - this.options.show = true; - - // Begin the animation - // Make sure that we start at a small width/height to avoid any - // flash of content - this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur()); - - // Start by showing the element - jQuery( this.elem ).show(); - }, - - // Simple 'hide' function - hide: function() { - // Remember where we started, so that we can go back to it later - this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); - this.options.hide = true; - - // Begin the animation - this.custom(this.cur(), 0); - }, - - // Each step of an animation - step: function( gotoEnd ) { - var t = fxNow || createFxNow(), - done = true, - elem = this.elem, - options = this.options, - i, n; - - if ( gotoEnd || t >= options.duration + this.startTime ) { - this.now = this.end; - this.pos = this.state = 1; - this.update(); - - options.animatedProperties[ this.prop ] = true; - - for ( i in options.animatedProperties ) { - if ( options.animatedProperties[i] !== true ) { - done = false; - } - } - - if ( done ) { - // Reset the overflow - if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) { - - jQuery.each( [ "", "X", "Y" ], function (index, value) { - elem.style[ "overflow" + value ] = options.overflow[index]; - }); - } - - // Hide the element if the "hide" operation was done - if ( options.hide ) { - jQuery(elem).hide(); - } - - // Reset the properties, if the item has been hidden or shown - if ( options.hide || options.show ) { - for ( var p in options.animatedProperties ) { - jQuery.style( elem, p, options.orig[p] ); - } - } - - // Execute the complete function - options.complete.call( elem ); - } - - return false; - - } else { - // classical easing cannot be used with an Infinity duration - if ( options.duration == Infinity ) { - this.now = t; - } else { - n = t - this.startTime; - this.state = n / options.duration; - - // Perform the easing function, defaults to swing - this.pos = jQuery.easing[ options.animatedProperties[ this.prop ] ]( this.state, n, 0, 1, options.duration ); - this.now = this.start + ((this.end - this.start) * this.pos); - } - // Perform the next step of the animation - this.update(); - } - - return true; - } -}; - -jQuery.extend( jQuery.fx, { - tick: function() { - for ( var timers = jQuery.timers, i = 0 ; i < timers.length ; ++i ) { - if ( !timers[i]() ) { - timers.splice(i--, 1); - } - } - - if ( !timers.length ) { - jQuery.fx.stop(); - } - }, - - interval: 13, - - stop: function() { - clearInterval( timerId ); - timerId = null; - }, - - speeds: { - slow: 600, - fast: 200, - // Default speed - _default: 400 - }, - - step: { - opacity: function( fx ) { - jQuery.style( fx.elem, "opacity", fx.now ); - }, - - _default: function( fx ) { - if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) { - fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit; - } else { - fx.elem[ fx.prop ] = fx.now; - } - } - } -}); - -if ( jQuery.expr && jQuery.expr.filters ) { - jQuery.expr.filters.animated = function( elem ) { - return jQuery.grep(jQuery.timers, function( fn ) { - return elem === fn.elem; - }).length; - }; -} - -// Try to restore the default display value of an element -function defaultDisplay( nodeName ) { - - if ( !elemdisplay[ nodeName ] ) { - - var body = document.body, - elem = jQuery( "<" + nodeName + ">" ).appendTo( body ), - display = elem.css( "display" ); - - elem.remove(); - - // If the simple way fails, - // get element's real default display by attaching it to a temp iframe - if ( display === "none" || display === "" ) { - // No iframe to use yet, so create it - if ( !iframe ) { - iframe = document.createElement( "iframe" ); - iframe.frameBorder = iframe.width = iframe.height = 0; - } - - body.appendChild( iframe ); - - // Create a cacheable copy of the iframe document on first call. - // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML - // document to it; WebKit & Firefox won't allow reusing the iframe document. - if ( !iframeDoc || !iframe.createElement ) { - iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document; - iframeDoc.write( ( document.compatMode === "CSS1Compat" ? "<!doctype html>" : "" ) + "<html><body>" ); - iframeDoc.close(); - } - - elem = iframeDoc.createElement( nodeName ); - - iframeDoc.body.appendChild( elem ); - - display = jQuery.css( elem, "display" ); - - body.removeChild( iframe ); - } - - // Store the correct default display - elemdisplay[ nodeName ] = display; - } - - return elemdisplay[ nodeName ]; -} - - - - -var rtable = /^t(?:able|d|h)$/i, - rroot = /^(?:body|html)$/i; - -if ( "getBoundingClientRect" in document.documentElement ) { - jQuery.fn.offset = function( options ) { - var elem = this[0], box; - - if ( options ) { - return this.each(function( i ) { - jQuery.offset.setOffset( this, options, i ); - }); - } - - if ( !elem || !elem.ownerDocument ) { - return null; - } - - if ( elem === elem.ownerDocument.body ) { - return jQuery.offset.bodyOffset( elem ); - } - - try { - box = elem.getBoundingClientRect(); - } catch(e) {} - - var doc = elem.ownerDocument, - docElem = doc.documentElement; - - // Make sure we're not dealing with a disconnected DOM node - if ( !box || !jQuery.contains( docElem, elem ) ) { - return box ? { top: box.top, left: box.left } : { top: 0, left: 0 }; - } - - var body = doc.body, - win = getWindow(doc), - clientTop = docElem.clientTop || body.clientTop || 0, - clientLeft = docElem.clientLeft || body.clientLeft || 0, - scrollTop = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop, - scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft, - top = box.top + scrollTop - clientTop, - left = box.left + scrollLeft - clientLeft; - - return { top: top, left: left }; - }; - -} else { - jQuery.fn.offset = function( options ) { - var elem = this[0]; - - if ( options ) { - return this.each(function( i ) { - jQuery.offset.setOffset( this, options, i ); - }); - } - - if ( !elem || !elem.ownerDocument ) { - return null; - } - - if ( elem === elem.ownerDocument.body ) { - return jQuery.offset.bodyOffset( elem ); - } - - jQuery.offset.initialize(); - - var computedStyle, - offsetParent = elem.offsetParent, - prevOffsetParent = elem, - doc = elem.ownerDocument, - docElem = doc.documentElement, - body = doc.body, - defaultView = doc.defaultView, - prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle, - top = elem.offsetTop, - left = elem.offsetLeft; - - while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) { - if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { - break; - } - - computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle; - top -= elem.scrollTop; - left -= elem.scrollLeft; - - if ( elem === offsetParent ) { - top += elem.offsetTop; - left += elem.offsetLeft; - - if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) { - top += parseFloat( computedStyle.borderTopWidth ) || 0; - left += parseFloat( computedStyle.borderLeftWidth ) || 0; - } - - prevOffsetParent = offsetParent; - offsetParent = elem.offsetParent; - } - - if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) { - top += parseFloat( computedStyle.borderTopWidth ) || 0; - left += parseFloat( computedStyle.borderLeftWidth ) || 0; - } - - prevComputedStyle = computedStyle; - } - - if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) { - top += body.offsetTop; - left += body.offsetLeft; - } - - if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { - top += Math.max( docElem.scrollTop, body.scrollTop ); - left += Math.max( docElem.scrollLeft, body.scrollLeft ); - } - - return { top: top, left: left }; - }; -} - -jQuery.offset = { - initialize: function() { - var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.css(body, "marginTop") ) || 0, - html = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>"; - - jQuery.extend( container.style, { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" } ); - - container.innerHTML = html; - body.insertBefore( container, body.firstChild ); - innerDiv = container.firstChild; - checkDiv = innerDiv.firstChild; - td = innerDiv.nextSibling.firstChild.firstChild; - - this.doesNotAddBorder = (checkDiv.offsetTop !== 5); - this.doesAddBorderForTableAndCells = (td.offsetTop === 5); - - checkDiv.style.position = "fixed"; - checkDiv.style.top = "20px"; - - // safari subtracts parent border width here which is 5px - this.supportsFixedPosition = (checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15); - checkDiv.style.position = checkDiv.style.top = ""; - - innerDiv.style.overflow = "hidden"; - innerDiv.style.position = "relative"; - - this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5); - - this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop); - - body.removeChild( container ); - jQuery.offset.initialize = jQuery.noop; - }, - - bodyOffset: function( body ) { - var top = body.offsetTop, - left = body.offsetLeft; - - jQuery.offset.initialize(); - - if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) { - top += parseFloat( jQuery.css(body, "marginTop") ) || 0; - left += parseFloat( jQuery.css(body, "marginLeft") ) || 0; - } - - return { top: top, left: left }; - }, - - setOffset: function( elem, options, i ) { - var position = jQuery.css( elem, "position" ); - - // set position first, in-case top/left are set even on static elem - if ( position === "static" ) { - elem.style.position = "relative"; - } - - var curElem = jQuery( elem ), - curOffset = curElem.offset(), - curCSSTop = jQuery.css( elem, "top" ), - curCSSLeft = jQuery.css( elem, "left" ), - calculatePosition = (position === "absolute" || position === "fixed") && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1, - props = {}, curPosition = {}, curTop, curLeft; - - // need to be able to calculate position if either top or left is auto and position is either absolute or fixed - if ( calculatePosition ) { - curPosition = curElem.position(); - curTop = curPosition.top; - curLeft = curPosition.left; - } else { - curTop = parseFloat( curCSSTop ) || 0; - curLeft = parseFloat( curCSSLeft ) || 0; - } - - if ( jQuery.isFunction( options ) ) { - options = options.call( elem, i, curOffset ); - } - - if (options.top != null) { - props.top = (options.top - curOffset.top) + curTop; - } - if (options.left != null) { - props.left = (options.left - curOffset.left) + curLeft; - } - - if ( "using" in options ) { - options.using.call( elem, props ); - } else { - curElem.css( props ); - } - } -}; - - -jQuery.fn.extend({ - position: function() { - if ( !this[0] ) { - return null; - } - - var elem = this[0], - - // Get *real* offsetParent - offsetParent = this.offsetParent(), - - // Get correct offsets - offset = this.offset(), - parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset(); - - // Subtract element margins - // note: when an element has margin: auto the offsetLeft and marginLeft - // are the same in Safari causing offset.left to incorrectly be 0 - offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0; - offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0; - - // Add offsetParent borders - parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0; - parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0; - - // Subtract the two offsets - return { - top: offset.top - parentOffset.top, - left: offset.left - parentOffset.left - }; - }, - - offsetParent: function() { - return this.map(function() { - var offsetParent = this.offsetParent || document.body; - while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) { - offsetParent = offsetParent.offsetParent; - } - return offsetParent; - }); - } -}); - - -// Create scrollLeft and scrollTop methods -jQuery.each( ["Left", "Top"], function( i, name ) { - var method = "scroll" + name; - - jQuery.fn[ method ] = function( val ) { - var elem, win; - - if ( val === undefined ) { - elem = this[ 0 ]; - - if ( !elem ) { - return null; - } - - win = getWindow( elem ); - - // Return the scroll offset - return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] : - jQuery.support.boxModel && win.document.documentElement[ method ] || - win.document.body[ method ] : - elem[ method ]; - } - - // Set the scroll offset - return this.each(function() { - win = getWindow( this ); - - if ( win ) { - win.scrollTo( - !i ? val : jQuery( win ).scrollLeft(), - i ? val : jQuery( win ).scrollTop() - ); - - } else { - this[ method ] = val; - } - }); - }; -}); - -function getWindow( elem ) { - return jQuery.isWindow( elem ) ? - elem : - elem.nodeType === 9 ? - elem.defaultView || elem.parentWindow : - false; -} - - - - -// Create width, height, innerHeight, innerWidth, outerHeight and outerWidth methods -jQuery.each([ "Height", "Width" ], function( i, name ) { - - var type = name.toLowerCase(); - - // innerHeight and innerWidth - jQuery.fn[ "inner" + name ] = function() { - var elem = this[0]; - return elem && elem.style ? - parseFloat( jQuery.css( elem, type, "padding" ) ) : - null; - }; - - // outerHeight and outerWidth - jQuery.fn[ "outer" + name ] = function( margin ) { - var elem = this[0]; - return elem && elem.style ? - parseFloat( jQuery.css( elem, type, margin ? "margin" : "border" ) ) : - null; - }; - - jQuery.fn[ type ] = function( size ) { - // Get window width or height - var elem = this[0]; - if ( !elem ) { - return size == null ? null : this; - } - - if ( jQuery.isFunction( size ) ) { - return this.each(function( i ) { - var self = jQuery( this ); - self[ type ]( size.call( this, i, self[ type ]() ) ); - }); - } - - if ( jQuery.isWindow( elem ) ) { - // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode - // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat - var docElemProp = elem.document.documentElement[ "client" + name ], - body = elem.document.body; - return elem.document.compatMode === "CSS1Compat" && docElemProp || - body && body[ "client" + name ] || docElemProp; - - // Get document width or height - } else if ( elem.nodeType === 9 ) { - // Either scroll[Width/Height] or offset[Width/Height], whichever is greater - return Math.max( - elem.documentElement["client" + name], - elem.body["scroll" + name], elem.documentElement["scroll" + name], - elem.body["offset" + name], elem.documentElement["offset" + name] - ); - - // Get or set width or height on the element - } else if ( size === undefined ) { - var orig = jQuery.css( elem, type ), - ret = parseFloat( orig ); - - return jQuery.isNaN( ret ) ? orig : ret; - - // Set the width or height on the element (default to pixels if value is unitless) - } else { - return this.css( type, typeof size === "string" ? size : size + "px" ); - } - }; - -}); - - -// Expose jQuery to the global object -window.jQuery = window.$ = jQuery; -})(window); diff --git a/addons/web/static/lib/jquery/jquery-1.7.2b1.js b/addons/web/static/lib/jquery/jquery-1.7.2.js index 1797ea9e6ab..3774ff98613 100644 --- a/addons/web/static/lib/jquery/jquery-1.7.2b1.js +++ b/addons/web/static/lib/jquery/jquery-1.7.2.js @@ -1,5 +1,5 @@ /*! - * jQuery JavaScript Library v1.7.2b1 + * jQuery JavaScript Library v1.7.2 * http://jquery.com/ * * Copyright 2011, John Resig @@ -11,7 +11,7 @@ * Copyright 2011, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * - * Date: Tue Jan 31 20:11:27 2012 -0500 + * Date: Wed Mar 21 12:46:34 2012 -0700 */ (function( window, undefined ) { @@ -210,7 +210,7 @@ jQuery.fn = jQuery.prototype = { selector: "", // The current version of jQuery being used - jquery: "1.7.2b1", + jquery: "1.7.2", // The default length of a jQuery object is 0 length: 0, @@ -578,6 +578,9 @@ jQuery.extend({ // Cross-browser xml parsing parseXML: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } var xml, tmp; try { if ( window.DOMParser ) { // Standard @@ -1362,7 +1365,6 @@ jQuery.support = (function() { select, opt, input, - marginDiv, fragment, tds, events, @@ -1449,6 +1451,9 @@ jQuery.support = (function() { pixelMargin: true }; + // jQuery.boxModel DEPRECATED in 1.3, use jQuery.support.boxModel instead + jQuery.boxModel = support.boxModel = (document.compatMode === "CSS1Compat"); + // Make sure checked status is properly cloned input.checked = true; support.noCloneChecked = input.cloneNode( true ).checked; @@ -1483,6 +1488,10 @@ jQuery.support = (function() { support.radioValue = input.value === "t"; input.setAttribute("checked", "checked"); + + // #11217 - WebKit loses check when the name is after the checked attribute + input.setAttribute( "name", "t" ); + div.appendChild( input ); fragment = document.createDocumentFragment(); fragment.appendChild( div.lastChild ); @@ -1497,23 +1506,6 @@ jQuery.support = (function() { fragment.removeChild( input ); fragment.appendChild( div ); - div.innerHTML = ""; - - // Check if div with explicit width and no margin-right incorrectly - // gets computed margin-right based on width of container. For more - // info see bug #3333 - // Fails in WebKit before Feb 2011 nightlies - // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right - if ( window.getComputedStyle ) { - marginDiv = document.createElement( "div" ); - marginDiv.style.width = "0"; - marginDiv.style.marginRight = "0"; - div.style.width = "2px"; - div.appendChild( marginDiv ); - support.reliableMarginRight = - ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; - } - // Technique from Juriy Zaytsev // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ // We only care about the case where non-standard event systems @@ -1539,12 +1531,13 @@ jQuery.support = (function() { fragment.removeChild( div ); // Null elements to avoid leaks in IE - fragment = select = opt = marginDiv = div = input = null; + fragment = select = opt = div = input = null; // Run tests that need a body at doc ready jQuery(function() { var container, outer, inner, table, td, offsetSupport, - conMarginTop, ptlm, vb, style, html, + marginDiv, conMarginTop, style, html, positionTopLeftWidthHeight, + paddingMarginBorderVisibility, paddingMarginBorder, body = document.getElementsByTagName("body")[0]; if ( !body ) { @@ -1553,15 +1546,16 @@ jQuery.support = (function() { } conMarginTop = 1; - ptlm = "position:absolute;top:0;left:0;width:1px;height:1px;margin:0;"; - vb = "visibility:hidden;border:0;"; - style = "style='" + ptlm + "border:5px solid #000;padding:0;'"; - html = "<div " + style + "><div></div></div>" + - "<table " + style + " cellpadding='0' cellspacing='0'>" + + paddingMarginBorder = "padding:0;margin:0;border:"; + positionTopLeftWidthHeight = "position:absolute;top:0;left:0;width:1px;height:1px;"; + paddingMarginBorderVisibility = paddingMarginBorder + "0;visibility:hidden;"; + style = "style='" + positionTopLeftWidthHeight + paddingMarginBorder + "5px solid #000;"; + html = "<div " + style + "display:block;'><div style='" + paddingMarginBorder + "0;display:block;overflow:hidden;'></div></div>" + + "<table " + style + "' cellpadding='0' cellspacing='0'>" + "<tr><td></td></tr></table>"; container = document.createElement("div"); - container.style.cssText = vb + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; + container.style.cssText = paddingMarginBorderVisibility + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; body.insertBefore( container, body.firstChild ); // Construct the test element @@ -1575,8 +1569,7 @@ jQuery.support = (function() { // display:none (it is still safe to use offsets if a parent element is // hidden; don safety goggles and see bug #4512 for more information). // (only IE 8 fails this test) - div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>"; - + div.innerHTML = "<table><tr><td style='" + paddingMarginBorder + "0;display:none'></td><td>t</td></tr></table>"; tds = div.getElementsByTagName( "td" ); isSupported = ( tds[ 0 ].offsetHeight === 0 ); @@ -1587,28 +1580,44 @@ jQuery.support = (function() { // (IE <= 8 fail this test) support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); - // Figure out if the W3C box model works as expected - div.innerHTML = ""; - div.style.width = div.style.paddingLeft = "1px"; - jQuery.boxModel = support.boxModel = div.offsetWidth === 2; + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. For more + // info see bug #3333 + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + if ( window.getComputedStyle ) { + div.innerHTML = ""; + marginDiv = document.createElement( "div" ); + marginDiv.style.width = "0"; + marginDiv.style.marginRight = "0"; + div.style.width = "2px"; + div.appendChild( marginDiv ); + support.reliableMarginRight = + ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; + } if ( typeof div.style.zoom !== "undefined" ) { // Check if natively block-level elements act like inline-block // elements when setting their display to 'inline' and giving // them layout // (IE < 8 does this) + div.innerHTML = ""; + div.style.width = div.style.padding = "1px"; + div.style.border = 0; + div.style.overflow = "hidden"; div.style.display = "inline"; div.style.zoom = 1; - support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 ); + support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); // Check if elements with layout shrink-wrap their children // (IE 6 does this) - div.style.display = ""; - div.innerHTML = "<div style='width:4px;'></div>"; - support.shrinkWrapBlocks = ( div.offsetWidth !== 2 ); + div.style.display = "block"; + div.style.overflow = "visible"; + div.innerHTML = "<div style='width:5px;'></div>"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); } - div.style.cssText = ptlm + vb; + div.style.cssText = positionTopLeftWidthHeight + paddingMarginBorderVisibility; div.innerHTML = html; outer = div.firstChild; @@ -1643,7 +1652,7 @@ jQuery.support = (function() { } body.removeChild( container ); - div = container = null; + marginDiv = div = container = null; jQuery.extend( support, offsetSupport ); }); @@ -1987,7 +1996,7 @@ function dataAttr( elem, key, data ) { data = data === "true" ? true : data === "false" ? false : data === "null" ? null : - jQuery.isNumeric( data ) ? parseFloat( data ) : + jQuery.isNumeric( data ) ? +data : rbrace.test( data ) ? jQuery.parseJSON( data ) : data; } catch( e ) {} @@ -2196,7 +2205,7 @@ jQuery.fn.extend({ } } resolve(); - return defer.promise(); + return defer.promise( object ); } }); @@ -2365,7 +2374,7 @@ jQuery.fn.extend({ if ( !arguments.length ) { if ( elem ) { - hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ]; + hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { return ret; @@ -2409,7 +2418,7 @@ jQuery.fn.extend({ }); } - hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ]; + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; // If set returns undefined, fall back to normal setting if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { @@ -2555,7 +2564,7 @@ jQuery.extend({ }, removeAttr: function( elem, value ) { - var propName, attrNames, name, l, + var propName, attrNames, name, l, isBool, i = 0; if ( value && elem.nodeType === 1 ) { @@ -2567,13 +2576,17 @@ jQuery.extend({ if ( name ) { propName = jQuery.propFix[ name ] || name; + isBool = rboolean.test( name ); // See #9699 for explanation of this approach (setting first, then removal) - jQuery.attr( elem, name, "" ); + // Do not do this for boolean attributes (see #10870) + if ( !isBool ) { + jQuery.attr( elem, name, "" ); + } elem.removeAttribute( getSetAttribute ? name : propName ); // Set corresponding property to false for boolean attributes - if ( rboolean.test( name ) && propName in elem ) { + if ( isBool && propName in elem ) { elem[ propName ] = false; } } @@ -2727,7 +2740,8 @@ if ( !getSetAttribute ) { fixSpecified = { name: true, - id: true + id: true, + coords: true }; // Use this for any attribute in IE6/7 @@ -3246,6 +3260,7 @@ jQuery.event = { delegateCount = handlers.delegateCount, args = [].slice.call( arguments, 0 ), run_all = !event.exclusive && !event.namespace, + special = jQuery.event.special[ event.type ] || {}, handlerQueue = [], i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related; @@ -3253,6 +3268,11 @@ jQuery.event = { args[0] = event; event.delegateTarget = this; + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + // Determine handlers that should run if there are delegated events // Avoid non-left-click bubbling in Firefox (#3861) if ( delegateCount && !(event.button && event.type === "click") ) { @@ -3262,7 +3282,7 @@ jQuery.event = { jqcur.context = this.ownerDocument || this; for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { - + // Don't process events on disabled elements (#6911, #8165) if ( cur.disabled !== true ) { selMatch = {}; @@ -3322,6 +3342,11 @@ jQuery.event = { } } + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + return event.result; }, @@ -3613,16 +3638,23 @@ if ( !jQuery.support.submitBubbles ) { form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; if ( form && !form._submit_attached ) { jQuery.event.add( form, "submit._submit", function( event ) { - // If form was submitted by the user, bubble the event up the tree - if ( this.parentNode && !event.isTrigger ) { - jQuery.event.simulate( "submit", this.parentNode, event, true ); - } + event._submit_bubble = true; }); form._submit_attached = true; } }); // return undefined since we don't need an event listener }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, teardown: function() { // Only need this for delegated form submit events @@ -5565,7 +5597,7 @@ jQuery.each({ return jQuery.dir( elem, "previousSibling", until ); }, siblings: function( elem ) { - return jQuery.sibling( elem.parentNode.firstChild, elem ); + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); }, children: function( elem ) { return jQuery.sibling( elem.firstChild ); @@ -5709,7 +5741,7 @@ var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figca rhtml = /<|&#?\w+;/, rnoInnerhtml = /<(?:script|style)/i, rnocache = /<(?:script|object|embed|option|style)/i, - rnoshimcache = new RegExp("<(?:" + nodeNames + ")", "i"), + rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), // checked="checked" or checked rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, rscriptType = /\/(java|ecma)script/i, @@ -6036,6 +6068,8 @@ jQuery.fn.extend({ jQuery.each( scripts, function( i, elem ) { if ( elem.src ) { jQuery.ajax({ + type: "GET", + global: false, url: elem.src, async: false, dataType: "script" @@ -6141,12 +6175,16 @@ function cloneFixAttributes( src, dest ) { // cloning other types of input fields } else if ( nodeName === "input" || nodeName === "textarea" ) { dest.defaultValue = src.defaultValue; + + // IE blanks contents when cloning scripts + } else if ( nodeName === "script" && dest.text !== src.text ) { + dest.text = src.text; } // Event data gets referenced instead of copied if the expando // gets copied too dest.removeAttribute( jQuery.expando ); - + // Clear flags for bubbling special change/submit events, they must // be reattached when the newly cloned events are first activated dest.removeAttribute( "_submit_attached" ); @@ -6275,7 +6313,7 @@ jQuery.extend({ destElements, i, // IE<=8 does not properly clone detached, unknown element nodes - clone = jQuery.support.html5Clone || !rnoshimcache.test( "<" + elem.nodeName ) ? + clone = jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ? elem.cloneNode( true ) : shimCloneNode( elem ); @@ -6325,7 +6363,8 @@ jQuery.extend({ }, clean: function( elems, context, fragment, scripts ) { - var checkScriptType; + var checkScriptType, script, j, + ret = []; context = context || document; @@ -6334,8 +6373,6 @@ jQuery.extend({ context = context.ownerDocument || context[0] && context[0].ownerDocument || document; } - var ret = [], j; - for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { if ( typeof elem === "number" ) { elem += ""; @@ -6357,7 +6394,9 @@ jQuery.extend({ var tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(), wrap = wrapMap[ tag ] || wrapMap._default, depth = wrap[0], - div = context.createElement("div"); + div = context.createElement("div"), + safeChildNodes = safeFragment.childNodes, + remove; // Append wrapper element to unknown element safe doc fragment if ( context === document ) { @@ -6402,6 +6441,21 @@ jQuery.extend({ } elem = div.childNodes; + + // Clear elements from DocumentFragment (safeFragment or otherwise) + // to avoid hoarding elements. Fixes #11356 + if ( div ) { + div.parentNode.removeChild( div ); + + // Guard against -1 index exceptions in FF3.6 + if ( safeChildNodes.length > 0 ) { + remove = safeChildNodes[ safeChildNodes.length - 1 ]; + + if ( remove && remove.parentNode ) { + remove.parentNode.removeChild( remove ); + } + } + } } } @@ -6430,16 +6484,17 @@ jQuery.extend({ return !elem.type || rscriptType.test( elem.type ); }; for ( i = 0; ret[i]; i++ ) { - if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) { - scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] ); + script = ret[i]; + if ( scripts && jQuery.nodeName( script, "script" ) && (!script.type || rscriptType.test( script.type )) ) { + scripts.push( script.parentNode ? script.parentNode.removeChild( script ) : script ); } else { - if ( ret[i].nodeType === 1 ) { - var jsTags = jQuery.grep( ret[i].getElementsByTagName( "script" ), checkScriptType ); + if ( script.nodeType === 1 ) { + var jsTags = jQuery.grep( script.getElementsByTagName( "script" ), checkScriptType ); ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) ); } - fragment.appendChild( ret[i] ); + fragment.appendChild( script ); } } } @@ -6659,7 +6714,7 @@ jQuery.extend({ } }); -// DEPRECATED, Use jQuery.css() instead +// DEPRECATED in 1.3, Use jQuery.css() instead jQuery.curCSS = jQuery.css; if ( document.defaultView && document.defaultView.getComputedStyle ) { @@ -7228,7 +7283,7 @@ jQuery.extend({ isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ), global: true, type: "GET", - contentType: "application/x-www-form-urlencoded", + contentType: "application/x-www-form-urlencoded; charset=UTF-8", processData: true, async: true, /* @@ -7927,8 +7982,7 @@ jQuery.ajaxSetup({ // Detect, normalize options and install callbacks for jsonp requests jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { - var inspectData = s.contentType === "application/x-www-form-urlencoded" && - ( typeof s.data === "string" ); + var inspectData = ( typeof s.data === "string" ) && /^application\/x\-www\-form\-urlencoded/.test( s.contentType ); if ( s.dataTypes[ 0 ] === "jsonp" || s.jsonp !== false && ( jsre.test( s.url ) || @@ -8229,7 +8283,13 @@ if ( jQuery.support.ajax ) { if ( xml && xml.documentElement /* #4958 */ ) { responses.xml = xml; } - responses.text = xhr.responseText; + + // When requesting binary data, IE6-9 will throw an exception + // on any attempt to access responseText (#11426) + try { + responses.text = xhr.responseText; + } catch( _ ) { + } // Firefox throws an exception when accessing // statusText for faulty cross-domain requests @@ -8456,7 +8516,7 @@ jQuery.fn.extend({ prop[ name ] = prop[ p ]; delete prop[ p ]; } - + if ( ( hooks = jQuery.cssHooks[ name ] ) && "expand" in hooks ) { replace = hooks.expand( prop[ name ] ); delete prop[ name ]; @@ -8699,11 +8759,11 @@ jQuery.extend({ }, easing: { - linear: function( p, n, firstNum, diff ) { - return firstNum + diff * p; + linear: function( p ) { + return p; }, - swing: function( p, n, firstNum, diff ) { - return ( ( -Math.cos( p*Math.PI ) / 2 ) + 0.5 ) * diff + firstNum; + swing: function( p ) { + return ( -Math.cos( p*Math.PI ) / 2 ) + 0.5; } }, @@ -8933,12 +8993,14 @@ jQuery.extend( jQuery.fx, { } }); -// Adds width/height step functions -// Do not set anything below 0 -jQuery.each([ "width", "height" ], function( i, prop ) { - jQuery.fx.step[ prop ] = function( fx ) { - jQuery.style( fx.elem, prop, Math.max(0, fx.now) + fx.unit ); - }; +// Ensure props that can't be negative don't go there on undershoot easing +jQuery.each( fxAttrs.concat.apply( [], fxAttrs ), function( i, prop ) { + // exclude marginTop, marginLeft, marginBottom and marginRight from this list + if ( prop.indexOf( "margin" ) ) { + jQuery.fx.step[ prop ] = function( fx ) { + jQuery.style( fx.elem, prop, Math.max(0, fx.now) + fx.unit ); + }; + } }); if ( jQuery.expr && jQuery.expr.filters ) { @@ -8975,7 +9037,7 @@ function defaultDisplay( nodeName ) { // document to it; WebKit & Firefox won't allow reusing the iframe document. if ( !iframeDoc || !iframe.createElement ) { iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document; - iframeDoc.write( ( document.compatMode === "CSS1Compat" ? "<!doctype html>" : "" ) + "<html><body>" ); + iframeDoc.write( ( jQuery.support.boxModel ? "<!doctype html>" : "" ) + "<html><body>" ); iframeDoc.close(); } @@ -9279,7 +9341,7 @@ jQuery.each( { Height: "height", Width: "width" }, function( name, type ) { // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat doc = elem.document; docElemProp = doc.documentElement[ clientProp ]; - return doc.compatMode === "CSS1Compat" && docElemProp || + return jQuery.support.boxModel && docElemProp || doc.body && doc.body[ clientProp ] || docElemProp; } @@ -9287,8 +9349,16 @@ jQuery.each( { Height: "height", Width: "width" }, function( name, type ) { if ( elem.nodeType === 9 ) { // Either scroll[Width/Height] or offset[Width/Height], whichever is greater doc = elem.documentElement; + + // when a window > document, IE6 reports a offset[Width/Height] > client[Width/Height] + // so we can't use max, as it'll choose the incorrect offset[Width/Height] + // instead we use the correct client[Width/Height] + // support:IE6 + if ( doc[ clientProp ] >= doc[ scrollProp ] ) { + return doc[ clientProp ]; + } + return Math.max( - doc[ clientProp ], elem.body[ scrollProp ], doc[ scrollProp ], elem.body[ offsetProp ], doc[ offsetProp ] ); diff --git a/addons/web/static/lib/json/json2.js b/addons/web/static/lib/json/json2.js deleted file mode 100755 index b4c02d3f08b..00000000000 --- a/addons/web/static/lib/json/json2.js +++ /dev/null @@ -1,480 +0,0 @@ -/* - http://www.JSON.org/json2.js - 2011-02-23 - - Public Domain. - - NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. - - See http://www.JSON.org/js.html - - - This code should be minified before deployment. - See http://javascript.crockford.com/jsmin.html - - USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO - NOT CONTROL. - - - This file creates a global JSON object containing two methods: stringify - and parse. - - JSON.stringify(value, replacer, space) - value any JavaScript value, usually an object or array. - - replacer an optional parameter that determines how object - values are stringified for objects. It can be a - function or an array of strings. - - space an optional parameter that specifies the indentation - of nested structures. If it is omitted, the text will - be packed without extra whitespace. If it is a number, - it will specify the number of spaces to indent at each - level. If it is a string (such as '\t' or ' '), - it contains the characters used to indent at each level. - - This method produces a JSON text from a JavaScript value. - - When an object value is found, if the object contains a toJSON - method, its toJSON method will be called and the result will be - stringified. A toJSON method does not serialize: it returns the - value represented by the name/value pair that should be serialized, - or undefined if nothing should be serialized. The toJSON method - will be passed the key associated with the value, and this will be - bound to the value - - For example, this would serialize Dates as ISO strings. - - Date.prototype.toJSON = function (key) { - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } - - return this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z'; - }; - - You can provide an optional replacer method. It will be passed the - key and value of each member, with this bound to the containing - object. The value that is returned from your method will be - serialized. If your method returns undefined, then the member will - be excluded from the serialization. - - If the replacer parameter is an array of strings, then it will be - used to select the members to be serialized. It filters the results - such that only members with keys listed in the replacer array are - stringified. - - Values that do not have JSON representations, such as undefined or - functions, will not be serialized. Such values in objects will be - dropped; in arrays they will be replaced with null. You can use - a replacer function to replace those with JSON values. - JSON.stringify(undefined) returns undefined. - - The optional space parameter produces a stringification of the - value that is filled with line breaks and indentation to make it - easier to read. - - If the space parameter is a non-empty string, then that string will - be used for indentation. If the space parameter is a number, then - the indentation will be that many spaces. - - Example: - - text = JSON.stringify(['e', {pluribus: 'unum'}]); - // text is '["e",{"pluribus":"unum"}]' - - - text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); - // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' - - text = JSON.stringify([new Date()], function (key, value) { - return this[key] instanceof Date ? - 'Date(' + this[key] + ')' : value; - }); - // text is '["Date(---current time---)"]' - - - JSON.parse(text, reviver) - This method parses a JSON text to produce an object or array. - It can throw a SyntaxError exception. - - The optional reviver parameter is a function that can filter and - transform the results. It receives each of the keys and values, - and its return value is used instead of the original value. - If it returns what it received, then the structure is not modified. - If it returns undefined then the member is deleted. - - Example: - - // Parse the text. Values that look like ISO date strings will - // be converted to Date objects. - - myData = JSON.parse(text, function (key, value) { - var a; - if (typeof value === 'string') { - a = -/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); - if (a) { - return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], - +a[5], +a[6])); - } - } - return value; - }); - - myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { - var d; - if (typeof value === 'string' && - value.slice(0, 5) === 'Date(' && - value.slice(-1) === ')') { - d = new Date(value.slice(5, -1)); - if (d) { - return d; - } - } - return value; - }); - - - This is a reference implementation. You are free to copy, modify, or - redistribute. -*/ - -/*jslint evil: true, strict: false, regexp: false */ - -/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, - call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, - getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, - lastIndex, length, parse, prototype, push, replace, slice, stringify, - test, toJSON, toString, valueOf -*/ - - -// Create a JSON object only if one does not already exist. We create the -// methods in a closure to avoid creating global variables. - -var JSON; -if (!JSON) { - JSON = {}; -} - -(function () { - "use strict"; - - function f(n) { - // Format integers to have at least two digits. - return n < 10 ? '0' + n : n; - } - - if (typeof Date.prototype.toJSON !== 'function') { - - Date.prototype.toJSON = function (key) { - - return isFinite(this.valueOf()) ? - this.getUTCFullYear() + '-' + - f(this.getUTCMonth() + 1) + '-' + - f(this.getUTCDate()) + 'T' + - f(this.getUTCHours()) + ':' + - f(this.getUTCMinutes()) + ':' + - f(this.getUTCSeconds()) + 'Z' : null; - }; - - String.prototype.toJSON = - Number.prototype.toJSON = - Boolean.prototype.toJSON = function (key) { - return this.valueOf(); - }; - } - - var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - gap, - indent, - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"' : '\\"', - '\\': '\\\\' - }, - rep; - - - function quote(string) { - -// If the string contains no control characters, no quote characters, and no -// backslash characters, then we can safely slap some quotes around it. -// Otherwise we must also replace the offending characters with safe escape -// sequences. - - escapable.lastIndex = 0; - return escapable.test(string) ? '"' + string.replace(escapable, function (a) { - var c = meta[a]; - return typeof c === 'string' ? c : - '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }) + '"' : '"' + string + '"'; - } - - - function str(key, holder) { - -// Produce a string from holder[key]. - - var i, // The loop counter. - k, // The member key. - v, // The member value. - length, - mind = gap, - partial, - value = holder[key]; - -// If the value has a toJSON method, call it to obtain a replacement value. - - if (value && typeof value === 'object' && - typeof value.toJSON === 'function') { - value = value.toJSON(key); - } - -// If we were called with a replacer function, then call the replacer to -// obtain a replacement value. - - if (typeof rep === 'function') { - value = rep.call(holder, key, value); - } - -// What happens next depends on the value's type. - - switch (typeof value) { - case 'string': - return quote(value); - - case 'number': - -// JSON numbers must be finite. Encode non-finite numbers as null. - - return isFinite(value) ? String(value) : 'null'; - - case 'boolean': - case 'null': - -// If the value is a boolean or null, convert it to a string. Note: -// typeof null does not produce 'null'. The case is included here in -// the remote chance that this gets fixed someday. - - return String(value); - -// If the type is 'object', we might be dealing with an object or an array or -// null. - - case 'object': - -// Due to a specification blunder in ECMAScript, typeof null is 'object', -// so watch out for that case. - - if (!value) { - return 'null'; - } - -// Make an array to hold the partial results of stringifying this object value. - - gap += indent; - partial = []; - -// Is the value an array? - - if (Object.prototype.toString.apply(value) === '[object Array]') { - -// The value is an array. Stringify every element. Use null as a placeholder -// for non-JSON values. - - length = value.length; - for (i = 0; i < length; i += 1) { - partial[i] = str(i, value) || 'null'; - } - -// Join all of the elements together, separated with commas, and wrap them in -// brackets. - - v = partial.length === 0 ? '[]' : gap ? - '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : - '[' + partial.join(',') + ']'; - gap = mind; - return v; - } - -// If the replacer is an array, use it to select the members to be stringified. - - if (rep && typeof rep === 'object') { - length = rep.length; - for (i = 0; i < length; i += 1) { - if (typeof rep[i] === 'string') { - k = rep[i]; - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } else { - -// Otherwise, iterate through all of the keys in the object. - - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } - -// Join all of the member texts together, separated with commas, -// and wrap them in braces. - - v = partial.length === 0 ? '{}' : gap ? - '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : - '{' + partial.join(',') + '}'; - gap = mind; - return v; - } - } - -// If the JSON object does not yet have a stringify method, give it one. - - if (typeof JSON.stringify !== 'function') { - JSON.stringify = function (value, replacer, space) { - -// The stringify method takes a value and an optional replacer, and an optional -// space parameter, and returns a JSON text. The replacer can be a function -// that can replace values, or an array of strings that will select the keys. -// A default replacer method can be provided. Use of the space parameter can -// produce text that is more easily readable. - - var i; - gap = ''; - indent = ''; - -// If the space parameter is a number, make an indent string containing that -// many spaces. - - if (typeof space === 'number') { - for (i = 0; i < space; i += 1) { - indent += ' '; - } - -// If the space parameter is a string, it will be used as the indent string. - - } else if (typeof space === 'string') { - indent = space; - } - -// If there is a replacer, it must be a function or an array. -// Otherwise, throw an error. - - rep = replacer; - if (replacer && typeof replacer !== 'function' && - (typeof replacer !== 'object' || - typeof replacer.length !== 'number')) { - throw new Error('JSON.stringify'); - } - -// Make a fake root object containing our value under the key of ''. -// Return the result of stringifying the value. - - return str('', {'': value}); - }; - } - - -// If the JSON object does not yet have a parse method, give it one. - - if (typeof JSON.parse !== 'function') { - JSON.parse = function (text, reviver) { - -// The parse method takes a text and an optional reviver function, and returns -// a JavaScript value if the text is a valid JSON text. - - var j; - - function walk(holder, key) { - -// The walk method is used to recursively walk the resulting structure so -// that modifications can be made. - - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; - } - } - } - } - return reviver.call(holder, key, value); - } - - -// Parsing happens in four stages. In the first stage, we replace certain -// Unicode characters with escape sequences. JavaScript handles many characters -// incorrectly, either silently deleting them, or treating them as line endings. - - text = String(text); - cx.lastIndex = 0; - if (cx.test(text)) { - text = text.replace(cx, function (a) { - return '\\u' + - ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }); - } - -// In the second stage, we run the text against regular expressions that look -// for non-JSON patterns. We are especially concerned with '()' and 'new' -// because they can cause invocation, and '=' because it can cause mutation. -// But just to be safe, we want to reject all unexpected forms. - -// We split the second stage into 4 regexp operations in order to work around -// crippling inefficiencies in IE's and Safari's regexp engines. First we -// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we -// replace all simple value tokens with ']' characters. Third, we delete all -// open brackets that follow a colon or comma or that begin the text. Finally, -// we look to see that the remaining characters are only whitespace or ']' or -// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. - - if (/^[\],:{}\s]*$/ - .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') - .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') - .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { - -// In the third stage we use the eval function to compile the text into a -// JavaScript structure. The '{' operator is subject to a syntactic ambiguity -// in JavaScript: it can begin a block or an object literal. We wrap the text -// in parens to eliminate the ambiguity. - - j = eval('(' + text + ')'); - -// In the optional fourth stage, we recursively walk the new structure, passing -// each name/value pair to a reviver function for possible transformation. - - return typeof reviver === 'function' ? - walk({'': j}, '') : j; - } - -// If the text is not JSON parseable, then a SyntaxError is thrown. - - throw new SyntaxError('JSON.parse'); - }; - } -}()); diff --git a/addons/web/static/lib/labjs/LAB.src.js b/addons/web/static/lib/labjs/LAB.src.js deleted file mode 100644 index 99807cd222e..00000000000 --- a/addons/web/static/lib/labjs/LAB.src.js +++ /dev/null @@ -1,514 +0,0 @@ -/*! LAB.js (LABjs :: Loading And Blocking JavaScript) - v2.0.3 (c) Kyle Simpson - MIT License -*/ - -(function(global){ - var _$LAB = global.$LAB, - - // constants for the valid keys of the options object - _UseLocalXHR = "UseLocalXHR", - _AlwaysPreserveOrder = "AlwaysPreserveOrder", - _AllowDuplicates = "AllowDuplicates", - _CacheBust = "CacheBust", - /*!START_DEBUG*/_Debug = "Debug",/*!END_DEBUG*/ - _BasePath = "BasePath", - - // stateless variables used across all $LAB instances - root_page = /^[^?#]*\//.exec(location.href)[0], - root_domain = /^\w+\:\/\/\/?[^\/]+/.exec(root_page)[0], - append_to = document.head || document.getElementsByTagName("head"), - - // inferences... ick, but still necessary - opera_or_gecko = (global.opera && Object.prototype.toString.call(global.opera) == "[object Opera]") || ("MozAppearance" in document.documentElement.style), - -/*!START_DEBUG*/ - // console.log() and console.error() wrappers - log_msg = function(){}, - log_error = log_msg, -/*!END_DEBUG*/ - - // feature sniffs (yay!) - test_script_elem = document.createElement("script"), - explicit_preloading = typeof test_script_elem.preload == "boolean", // http://wiki.whatwg.org/wiki/Script_Execution_Control#Proposal_1_.28Nicholas_Zakas.29 - real_preloading = explicit_preloading || (test_script_elem.readyState && test_script_elem.readyState == "uninitialized"), // will a script preload with `src` set before DOM append? - script_ordered_async = !real_preloading && test_script_elem.async === true, // http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order - - // XHR preloading (same-domain) and cache-preloading (remote-domain) are the fallbacks (for some browsers) - xhr_or_cache_preloading = !real_preloading && !script_ordered_async && !opera_or_gecko - ; - -/*!START_DEBUG*/ - // define console wrapper functions if applicable - if (global.console && global.console.log) { - if (!global.console.error) global.console.error = global.console.log; - log_msg = function(msg) { global.console.log(msg); }; - log_error = function(msg,err) { global.console.error(msg,err); }; - } -/*!END_DEBUG*/ - - // test for function - function is_func(func) { return Object.prototype.toString.call(func) == "[object Function]"; } - - // test for array - function is_array(arr) { return Object.prototype.toString.call(arr) == "[object Array]"; } - - // make script URL absolute/canonical - function canonical_uri(src,base_path) { - var absolute_regex = /^\w+\:\/\//; - - // is `src` is protocol-relative (begins with // or ///), prepend protocol - if (/^\/\/\/?/.test(src)) { - src = location.protocol + src; - } - // is `src` page-relative? (not an absolute URL, and not a domain-relative path, beginning with /) - else if (!absolute_regex.test(src) && src.charAt(0) != "/") { - // prepend `base_path`, if any - src = (base_path || "") + src; - } - // make sure to return `src` as absolute - return absolute_regex.test(src) ? src : ((src.charAt(0) == "/" ? root_domain : root_page) + src); - } - - // merge `source` into `target` - function merge_objs(source,target) { - for (var k in source) { if (source.hasOwnProperty(k)) { - target[k] = source[k]; // TODO: does this need to be recursive for our purposes? - }} - return target; - } - - // does the chain group have any ready-to-execute scripts? - function check_chain_group_scripts_ready(chain_group) { - var any_scripts_ready = false; - for (var i=0; i<chain_group.scripts.length; i++) { - if (chain_group.scripts[i].ready && chain_group.scripts[i].exec_trigger) { - any_scripts_ready = true; - chain_group.scripts[i].exec_trigger(); - chain_group.scripts[i].exec_trigger = null; - } - } - return any_scripts_ready; - } - - // creates a script load listener - function create_script_load_listener(elem,registry_item,flag,onload) { - elem.onload = elem.onreadystatechange = function() { - if ((elem.readyState && elem.readyState != "complete" && elem.readyState != "loaded") || registry_item[flag]) return; - elem.onload = elem.onreadystatechange = null; - onload(); - }; - } - - // script executed handler - function script_executed(registry_item) { - registry_item.ready = registry_item.finished = true; - for (var i=0; i<registry_item.finished_listeners.length; i++) { - registry_item.finished_listeners[i](); - } - registry_item.ready_listeners = []; - registry_item.finished_listeners = []; - } - - // make the request for a scriptha - function request_script(chain_opts,script_obj,registry_item,onload,preload_this_script) { - // setTimeout() "yielding" prevents some weird race/crash conditions in older browsers - setTimeout(function(){ - var script, src = script_obj.real_src, xhr; - - // don't proceed until `append_to` is ready to append to - if ("item" in append_to) { // check if `append_to` ref is still a live node list - if (!append_to[0]) { // `append_to` node not yet ready - // try again in a little bit -- note: will re-call the anonymous function in the outer setTimeout, not the parent `request_script()` - setTimeout(arguments.callee,25); - return; - } - // reassign from live node list ref to pure node ref -- avoids nasty IE bug where changes to DOM invalidate live node lists - append_to = append_to[0]; - } - script = document.createElement("script"); - if (script_obj.type) script.type = script_obj.type; - if (script_obj.charset) script.charset = script_obj.charset; - - // should preloading be used for this script? - if (preload_this_script) { - // real script preloading? - if (real_preloading) { - /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload: "+src);/*!END_DEBUG*/ - registry_item.elem = script; - if (explicit_preloading) { // explicit preloading (aka, Zakas' proposal) - script.preload = true; - script.onpreload = onload; - } - else { - script.onreadystatechange = function(){ - if (script.readyState == "loaded") onload(); - }; - } - script.src = src; - // NOTE: no append to DOM yet, appending will happen when ready to execute - } - // same-domain and XHR allowed? use XHR preloading - else if (preload_this_script && src.indexOf(root_domain) == 0 && chain_opts[_UseLocalXHR]) { - xhr = new XMLHttpRequest(); // note: IE never uses XHR (it supports true preloading), so no more need for ActiveXObject fallback for IE <= 7 - /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload (xhr): "+src);/*!END_DEBUG*/ - xhr.onreadystatechange = function() { - if (xhr.readyState == 4) { - xhr.onreadystatechange = function(){}; // fix a memory leak in IE - registry_item.text = xhr.responseText + "\n//@ sourceURL=" + src; // http://blog.getfirebug.com/2009/08/11/give-your-eval-a-name-with-sourceurl/ - onload(); - } - }; - xhr.open("GET",src); - xhr.send(); - } - // as a last resort, use cache-preloading - else { - /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script preload (cache): "+src);/*!END_DEBUG*/ - script.type = "text/cache-script"; - create_script_load_listener(script,registry_item,"ready",function() { - append_to.removeChild(script); - onload(); - }); - script.src = src; - append_to.insertBefore(script,append_to.firstChild); - } - } - // use async=false for ordered async? parallel-load-serial-execute http://wiki.whatwg.org/wiki/Dynamic_Script_Execution_Order - else if (script_ordered_async) { - /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script load (ordered async): "+src);/*!END_DEBUG*/ - script.async = false; - create_script_load_listener(script,registry_item,"finished",onload); - script.src = src; - append_to.insertBefore(script,append_to.firstChild); - } - // otherwise, just a normal script element - else { - /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("start script load: "+src);/*!END_DEBUG*/ - create_script_load_listener(script,registry_item,"finished",onload); - script.src = src; - append_to.insertBefore(script,append_to.firstChild); - } - },0); - } - - // create a clean instance of $LAB - function create_sandbox() { - var global_defaults = {}, - can_use_preloading = real_preloading || xhr_or_cache_preloading, - queue = [], - registry = {}, - instanceAPI - ; - - // global defaults - global_defaults[_UseLocalXHR] = true; - global_defaults[_AlwaysPreserveOrder] = false; - global_defaults[_AllowDuplicates] = false; - global_defaults[_CacheBust] = false; - /*!START_DEBUG*/global_defaults[_Debug] = false;/*!END_DEBUG*/ - global_defaults[_BasePath] = ""; - - // execute a script that has been preloaded already - function execute_preloaded_script(chain_opts,script_obj,registry_item) { - var script; - - function preload_execute_finished() { - if (script != null) { // make sure this only ever fires once - script = null; - script_executed(registry_item); - } - } - - if (registry[script_obj.src].finished) return; - if (!chain_opts[_AllowDuplicates]) registry[script_obj.src].finished = true; - - script = registry_item.elem || document.createElement("script"); - if (script_obj.type) script.type = script_obj.type; - if (script_obj.charset) script.charset = script_obj.charset; - create_script_load_listener(script,registry_item,"finished",preload_execute_finished); - - // script elem was real-preloaded - if (registry_item.elem) { - registry_item.elem = null; - } - // script was XHR preloaded - else if (registry_item.text) { - script.onload = script.onreadystatechange = null; // script injection doesn't fire these events - script.text = registry_item.text; - } - // script was cache-preloaded - else { - script.src = script_obj.real_src; - } - append_to.insertBefore(script,append_to.firstChild); - - // manually fire execution callback for injected scripts, since events don't fire - if (registry_item.text) { - preload_execute_finished(); - } - } - - // process the script request setup - function do_script(chain_opts,script_obj,chain_group,preload_this_script) { - var registry_item, - registry_items, - ready_cb = function(){ script_obj.ready_cb(script_obj,function(){ execute_preloaded_script(chain_opts,script_obj,registry_item); }); }, - finished_cb = function(){ script_obj.finished_cb(script_obj,chain_group); } - ; - - script_obj.src = canonical_uri(script_obj.src,chain_opts[_BasePath]); - script_obj.real_src = script_obj.src + - // append cache-bust param to URL? - (chain_opts[_CacheBust] ? ((/\?.*$/.test(script_obj.src) ? "&_" : "?_") + ~~(Math.random()*1E9) + "=") : "") - ; - - if (!registry[script_obj.src]) registry[script_obj.src] = {items:[],finished:false}; - registry_items = registry[script_obj.src].items; - - // allowing duplicates, or is this the first recorded load of this script? - if (chain_opts[_AllowDuplicates] || registry_items.length == 0) { - registry_item = registry_items[registry_items.length] = { - ready:false, - finished:false, - ready_listeners:[ready_cb], - finished_listeners:[finished_cb] - }; - - request_script(chain_opts,script_obj,registry_item, - // which callback type to pass? - ( - (preload_this_script) ? // depends on script-preloading - function(){ - registry_item.ready = true; - for (var i=0; i<registry_item.ready_listeners.length; i++) { - registry_item.ready_listeners[i](); - } - registry_item.ready_listeners = []; - } : - function(){ script_executed(registry_item); } - ), - // signal if script-preloading should be used or not - preload_this_script - ); - } - else { - registry_item = registry_items[0]; - if (registry_item.finished) { - finished_cb(); - } - else { - registry_item.finished_listeners.push(finished_cb); - } - } - } - - // creates a closure for each separate chain spawned from this $LAB instance, to keep state cleanly separated between chains - function create_chain() { - var chainedAPI, - chain_opts = merge_objs(global_defaults,{}), - chain = [], - exec_cursor = 0, - scripts_currently_loading = false, - group - ; - - // called when a script has finished preloading - function chain_script_ready(script_obj,exec_trigger) { - /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("script preload finished: "+script_obj.real_src);/*!END_DEBUG*/ - script_obj.ready = true; - script_obj.exec_trigger = exec_trigger; - advance_exec_cursor(); // will only check for 'ready' scripts to be executed - } - - // called when a script has finished executing - function chain_script_executed(script_obj,chain_group) { - /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("script execution finished: "+script_obj.real_src);/*!END_DEBUG*/ - script_obj.ready = script_obj.finished = true; - script_obj.exec_trigger = null; - // check if chain group is all finished - for (var i=0; i<chain_group.scripts.length; i++) { - if (!chain_group.scripts[i].finished) return; - } - // chain_group is all finished if we get this far - chain_group.finished = true; - advance_exec_cursor(); - } - - // main driver for executing each part of the chain - function advance_exec_cursor() { - while (exec_cursor < chain.length) { - if (is_func(chain[exec_cursor])) { - /*!START_DEBUG*/if (chain_opts[_Debug]) log_msg("$LAB.wait() executing: "+chain[exec_cursor]);/*!END_DEBUG*/ - try { chain[exec_cursor++](); } catch (err) { - /*!START_DEBUG*/if (chain_opts[_Debug]) log_error("$LAB.wait() error caught: ",err);/*!END_DEBUG*/ - } - continue; - } - else if (!chain[exec_cursor].finished) { - if (check_chain_group_scripts_ready(chain[exec_cursor])) continue; - break; - } - exec_cursor++; - } - // we've reached the end of the chain (so far) - if (exec_cursor == chain.length) { - scripts_currently_loading = false; - group = false; - } - } - - // setup next chain script group - function init_script_chain_group() { - if (!group || !group.scripts) { - chain.push(group = {scripts:[],finished:true}); - } - } - - // API for $LAB chains - chainedAPI = { - // start loading one or more scripts - script:function(){ - for (var i=0; i<arguments.length; i++) { - (function(script_obj,script_list){ - var splice_args; - - if (!is_array(script_obj)) { - script_list = [script_obj]; - } - for (var j=0; j<script_list.length; j++) { - init_script_chain_group(); - script_obj = script_list[j]; - - if (is_func(script_obj)) script_obj = script_obj(); - if (!script_obj) continue; - if (is_array(script_obj)) { - // set up an array of arguments to pass to splice() - splice_args = [].slice.call(script_obj); // first include the actual array elements we want to splice in - splice_args.unshift(j,1); // next, put the `index` and `howMany` parameters onto the beginning of the splice-arguments array - [].splice.apply(script_list,splice_args); // use the splice-arguments array as arguments for splice() - j--; // adjust `j` to account for the loop's subsequent `j++`, so that the next loop iteration uses the same `j` index value - continue; - } - if (typeof script_obj == "string") script_obj = {src:script_obj}; - script_obj = merge_objs(script_obj,{ - ready:false, - ready_cb:chain_script_ready, - finished:false, - finished_cb:chain_script_executed - }); - group.finished = false; - group.scripts.push(script_obj); - - do_script(chain_opts,script_obj,group,(can_use_preloading && scripts_currently_loading)); - scripts_currently_loading = true; - - if (chain_opts[_AlwaysPreserveOrder]) chainedAPI.wait(); - } - })(arguments[i],arguments[i]); - } - return chainedAPI; - }, - // force LABjs to pause in execution at this point in the chain, until the execution thus far finishes, before proceeding - wait:function(){ - if (arguments.length > 0) { - for (var i=0; i<arguments.length; i++) { - chain.push(arguments[i]); - } - group = chain[chain.length-1]; - } - else group = false; - - advance_exec_cursor(); - - return chainedAPI; - } - }; - - // the first chain link API (includes `setOptions` only this first time) - return { - script:chainedAPI.script, - wait:chainedAPI.wait, - setOptions:function(opts){ - merge_objs(opts,chain_opts); - return chainedAPI; - } - }; - } - - // API for each initial $LAB instance (before chaining starts) - instanceAPI = { - // main API functions - setGlobalDefaults:function(opts){ - merge_objs(opts,global_defaults); - return instanceAPI; - }, - setOptions:function(){ - return create_chain().setOptions.apply(null,arguments); - }, - script:function(){ - return create_chain().script.apply(null,arguments); - }, - wait:function(){ - return create_chain().wait.apply(null,arguments); - }, - - // built-in queuing for $LAB `script()` and `wait()` calls - // useful for building up a chain programmatically across various script locations, and simulating - // execution of the chain - queueScript:function(){ - queue[queue.length] = {type:"script", args:[].slice.call(arguments)}; - return instanceAPI; - }, - queueWait:function(){ - queue[queue.length] = {type:"wait", args:[].slice.call(arguments)}; - return instanceAPI; - }, - runQueue:function(){ - var $L = instanceAPI, len=queue.length, i=len, val; - for (;--i>=0;) { - val = queue.shift(); - $L = $L[val.type].apply(null,val.args); - } - return $L; - }, - - // rollback `[global].$LAB` to what it was before this file was loaded, the return this current instance of $LAB - noConflict:function(){ - global.$LAB = _$LAB; - return instanceAPI; - }, - - // create another clean instance of $LAB - sandbox:function(){ - return create_sandbox(); - } - }; - - return instanceAPI; - } - - // create the main instance of $LAB - global.$LAB = create_sandbox(); - - - /* The following "hack" was suggested by Andrea Giammarchi and adapted from: http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html - NOTE: this hack only operates in FF and then only in versions where document.readyState is not present (FF < 3.6?). - - The hack essentially "patches" the **page** that LABjs is loaded onto so that it has a proper conforming document.readyState, so that if a script which does - proper and safe dom-ready detection is loaded onto a page, after dom-ready has passed, it will still be able to detect this state, by inspecting the now hacked - document.readyState property. The loaded script in question can then immediately trigger any queued code executions that were waiting for the DOM to be ready. - For instance, jQuery 1.4+ has been patched to take advantage of document.readyState, which is enabled by this hack. But 1.3.2 and before are **not** safe or - fixed by this hack, and should therefore **not** be lazy-loaded by script loader tools such as LABjs. - */ - (function(addEvent,domLoaded,handler){ - if (document.readyState == null && document[addEvent]){ - document.readyState = "loading"; - document[addEvent](domLoaded,handler = function(){ - document.removeEventListener(domLoaded,handler,false); - document.readyState = "complete"; - },false); - } - })("addEventListener","DOMContentLoaded"); - -})(this);
\ No newline at end of file diff --git a/addons/web/static/lib/qunit/qunit.css b/addons/web/static/lib/qunit/qunit.css index 58101ea34ce..257b224ff44 100644 --- a/addons/web/static/lib/qunit/qunit.css +++ b/addons/web/static/lib/qunit/qunit.css @@ -1,5 +1,5 @@ /** - * QUnit v1.4.0pre - A JavaScript Unit Testing Framework + * QUnit v1.9.0 - A JavaScript Unit Testing Framework * * http://docs.jquery.com/QUnit * @@ -38,10 +38,10 @@ line-height: 1em; font-weight: normal; - border-radius: 15px 15px 0 0; - -moz-border-radius: 15px 15px 0 0; - -webkit-border-top-right-radius: 15px; - -webkit-border-top-left-radius: 15px; + border-radius: 5px 5px 0 0; + -moz-border-radius: 5px 5px 0 0; + -webkit-border-top-right-radius: 5px; + -webkit-border-top-left-radius: 5px; } #qunit-header a { @@ -54,8 +54,9 @@ color: #fff; } -#qunit-header label { +#qunit-testrunner-toolbar label { display: inline-block; + padding: 0 .5em 0 .1em; } #qunit-banner { @@ -112,13 +113,9 @@ background-color: #fff; - border-radius: 15px; - -moz-border-radius: 15px; - -webkit-border-radius: 15px; - - box-shadow: inset 0px 2px 13px #999; - -moz-box-shadow: inset 0px 2px 13px #999; - -webkit-box-shadow: inset 0px 2px 13px #999; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; } #qunit-tests table { @@ -161,8 +158,7 @@ #qunit-tests b.failed { color: #710909; } #qunit-tests li li { - margin: 0.5em; - padding: 0.4em 0.5em 0.4em 0.5em; + padding: 5px; background-color: #fff; border-bottom: none; list-style-position: inside; @@ -171,9 +167,9 @@ /*** Passing Styles */ #qunit-tests li li.pass { - color: #5E740B; + color: #3c510c; background-color: #fff; - border-left: 26px solid #C6E746; + border-left: 10px solid #C6E746; } #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } @@ -189,15 +185,15 @@ #qunit-tests li li.fail { color: #710909; background-color: #fff; - border-left: 26px solid #EE5757; + border-left: 10px solid #EE5757; white-space: pre; } #qunit-tests > li:last-child { - border-radius: 0 0 15px 15px; - -moz-border-radius: 0 0 15px 15px; - -webkit-border-bottom-right-radius: 15px; - -webkit-border-bottom-left-radius: 15px; + border-radius: 0 0 5px 5px; + -moz-border-radius: 0 0 5px 5px; + -webkit-border-bottom-right-radius: 5px; + -webkit-border-bottom-left-radius: 5px; } #qunit-tests .fail { color: #000000; background-color: #EE5757; } @@ -220,6 +216,9 @@ border-bottom: 1px solid white; } +#qunit-testresult .module-name { + font-weight: bold; +} /** Fixture */ diff --git a/addons/web/static/lib/qunit/qunit.js b/addons/web/static/lib/qunit/qunit.js index b71381313c7..65625336453 100644 --- a/addons/web/static/lib/qunit/qunit.js +++ b/addons/web/static/lib/qunit/qunit.js @@ -1,5 +1,5 @@ /** - * QUnit v1.4.0pre - A JavaScript Unit Testing Framework + * QUnit v1.9.0 - A JavaScript Unit Testing Framework * * http://docs.jquery.com/QUnit * @@ -8,75 +8,89 @@ * or GPL (GPL-LICENSE.txt) licenses. */ -(function(window) { +(function( window ) { -var defined = { +var QUnit, + config, + onErrorFnPrev, + testId = 0, + fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""), + toString = Object.prototype.toString, + hasOwn = Object.prototype.hasOwnProperty, + defined = { setTimeout: typeof window.setTimeout !== "undefined", sessionStorage: (function() { var x = "qunit-test-string"; try { - sessionStorage.setItem(x, x); - sessionStorage.removeItem(x); + sessionStorage.setItem( x, x ); + sessionStorage.removeItem( x ); return true; - } catch(e) { + } catch( e ) { return false; } - })() + }()) }; -var testId = 0, - toString = Object.prototype.toString, - hasOwn = Object.prototype.hasOwnProperty; - -var Test = function(name, testName, expected, async, callback) { - this.name = name; - this.testName = testName; - this.expected = expected; - this.async = async; - this.callback = callback; +function Test( settings ) { + extend( this, settings ); this.assertions = []; -}; + this.testNumber = ++Test.count; +} + +Test.count = 0; + Test.prototype = { init: function() { - var tests = id("qunit-tests"); - if (tests) { - var b = document.createElement("strong"); - b.innerHTML = "Running " + this.name; - var li = document.createElement("li"); - li.appendChild( b ); - li.className = "running"; - li.id = this.id = "test-output" + testId++; + var a, b, li, + tests = id( "qunit-tests" ); + + if ( tests ) { + b = document.createElement( "strong" ); + b.innerHTML = this.name; + + // `a` initialized at top of scope + a = document.createElement( "a" ); + a.innerHTML = "Rerun"; + a.href = QUnit.url({ testNumber: this.testNumber }); + + li = document.createElement( "li" ); + li.appendChild( b ); + li.appendChild( a ); + li.className = "running"; + li.id = this.id = "qunit-test-output" + testId++; + tests.appendChild( li ); } }, setup: function() { - if (this.module != config.previousModule) { + if ( this.module !== config.previousModule ) { if ( config.previousModule ) { - runLoggingCallbacks('moduleDone', QUnit, { + runLoggingCallbacks( "moduleDone", QUnit, { name: config.previousModule, failed: config.moduleStats.bad, passed: config.moduleStats.all - config.moduleStats.bad, total: config.moduleStats.all - } ); + }); } config.previousModule = this.module; config.moduleStats = { all: 0, bad: 0 }; - runLoggingCallbacks( 'moduleStart', QUnit, { + runLoggingCallbacks( "moduleStart", QUnit, { name: this.module - } ); - } else if (config.autorun) { - runLoggingCallbacks( 'moduleStart', QUnit, { + }); + } else if ( config.autorun ) { + runLoggingCallbacks( "moduleStart", QUnit, { name: this.module - } ); + }); } config.current = this; + this.testEnvironment = extend({ setup: function() {}, teardown: function() {} - }, this.moduleTestEnvironment); + }, this.moduleTestEnvironment ); - runLoggingCallbacks( 'testStart', QUnit, { + runLoggingCallbacks( "testStart", QUnit, { name: this.testName, module: this.module }); @@ -85,31 +99,41 @@ Test.prototype = { // TODO why?? QUnit.current_testEnvironment = this.testEnvironment; + if ( !config.pollution ) { + saveGlobal(); + } + if ( config.notrycatch ) { + this.testEnvironment.setup.call( this.testEnvironment ); + return; + } try { - if ( !config.pollution ) { - saveGlobal(); - } - - this.testEnvironment.setup.call(this.testEnvironment); - } catch(e) { - QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message ); + this.testEnvironment.setup.call( this.testEnvironment ); + } catch( e ) { + QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); } }, run: function() { config.current = this; + + var running = id( "qunit-testresult" ); + + if ( running ) { + running.innerHTML = "Running: <br/>" + this.name; + } + if ( this.async ) { QUnit.stop(); } if ( config.notrycatch ) { - this.callback.call(this.testEnvironment); + this.callback.call( this.testEnvironment, QUnit.assert ); return; } + try { - this.callback.call(this.testEnvironment); - } catch(e) { - fail("Test " + this.testName + " died, exception and test follows", e, this.callback); - QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); + this.callback.call( this.testEnvironment, QUnit.assert ); + } catch( e ) { + QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + e.message, extractStacktrace( e, 0 ) ); // else next test will carry the responsibility saveGlobal(); @@ -121,34 +145,46 @@ Test.prototype = { }, teardown: function() { config.current = this; - try { - this.testEnvironment.teardown.call(this.testEnvironment); - checkPollution(); - } catch(e) { - QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message ); + if ( config.notrycatch ) { + this.testEnvironment.teardown.call( this.testEnvironment ); + return; + } else { + try { + this.testEnvironment.teardown.call( this.testEnvironment ); + } catch( e ) { + QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) ); + } } + checkPollution(); }, finish: function() { config.current = this; - if ( this.expected != null && this.expected != this.assertions.length ) { - QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); + if ( config.requireExpects && this.expected == null ) { + QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack ); + } else if ( this.expected != null && this.expected != this.assertions.length ) { + QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack ); + } else if ( this.expected == null && !this.assertions.length ) { + QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack ); } - var good = 0, bad = 0, - tests = id("qunit-tests"); + var assertion, a, b, i, li, ol, + test = this, + good = 0, + bad = 0, + tests = id( "qunit-tests" ); config.stats.all += this.assertions.length; config.moduleStats.all += this.assertions.length; if ( tests ) { - var ol = document.createElement("ol"); + ol = document.createElement( "ol" ); - for ( var i = 0; i < this.assertions.length; i++ ) { - var assertion = this.assertions[i]; + for ( i = 0; i < this.assertions.length; i++ ) { + assertion = this.assertions[i]; - var li = document.createElement("li"); + li = document.createElement( "li" ); li.className = assertion.result ? "pass" : "fail"; - li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); + li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" ); ol.appendChild( li ); if ( assertion.result ) { @@ -162,49 +198,48 @@ Test.prototype = { // store result when possible if ( QUnit.config.reorder && defined.sessionStorage ) { - if (bad) { - sessionStorage.setItem("qunit-" + this.module + "-" + this.testName, bad); + if ( bad ) { + sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad ); } else { - sessionStorage.removeItem("qunit-" + this.module + "-" + this.testName); + sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName ); } } - if (bad == 0) { + if ( bad === 0 ) { ol.style.display = "none"; } - var b = document.createElement("strong"); + // `b` initialized at top of scope + b = document.createElement( "strong" ); b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>"; - var a = document.createElement("a"); - a.innerHTML = "Rerun"; - a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); - addEvent(b, "click", function() { var next = b.nextSibling.nextSibling, display = next.style.display; next.style.display = display === "none" ? "block" : "none"; }); - addEvent(b, "dblclick", function(e) { + addEvent(b, "dblclick", function( e ) { var target = e && e.target ? e.target : window.event.srcElement; if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { target = target.parentNode; } if ( window.location && target.nodeName.toLowerCase() === "strong" ) { - window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); + window.location = QUnit.url({ testNumber: test.testNumber }); } }); - var li = id(this.id); + // `li` initialized at top of scope + li = id( this.id ); li.className = bad ? "fail" : "pass"; li.removeChild( li.firstChild ); + a = li.firstChild; li.appendChild( b ); - li.appendChild( a ); + li.appendChild ( a ); li.appendChild( ol ); } else { - for ( var i = 0; i < this.assertions.length; i++ ) { + for ( i = 0; i < this.assertions.length; i++ ) { if ( !this.assertions[i].result ) { bad++; config.stats.bad++; @@ -213,23 +248,23 @@ Test.prototype = { } } - try { - QUnit.reset(); - } catch(e) { - fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset); - } - - runLoggingCallbacks( 'testDone', QUnit, { + runLoggingCallbacks( "testDone", QUnit, { name: this.testName, module: this.module, failed: bad, passed: this.assertions.length - bad, total: this.assertions.length - } ); + }); + + QUnit.reset(); + + config.current = undefined; }, queue: function() { - var test = this; + var bad, + test = this; + synchronize(function() { test.init(); }); @@ -248,36 +283,42 @@ Test.prototype = { test.finish(); }); } + + // `bad` initialized at top of scope // defer when previous test run passed, if storage is available - var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.module + "-" + this.testName); - if (bad) { + bad = QUnit.config.reorder && defined.sessionStorage && + +sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName ); + + if ( bad ) { run(); } else { - synchronize(run, true); - }; + synchronize( run, true ); + } } - }; -var QUnit = { +// Root QUnit object. +// `QUnit` initialized at top of scope +QUnit = { // call on start of module test to prepend name to all tests - module: function(name, testEnvironment) { + module: function( name, testEnvironment ) { config.currentModule = name; config.currentModuleTestEnviroment = testEnvironment; }, - asyncTest: function(testName, expected, callback) { + asyncTest: function( testName, expected, callback ) { if ( arguments.length === 2 ) { callback = expected; expected = null; } - QUnit.test(testName, expected, callback, true); + QUnit.test( testName, expected, callback, true ); }, - test: function(testName, expected, callback, async) { - var name = '<span class="test-name">' + escapeInnerText(testName) + '</span>'; + test: function( testName, expected, callback, async ) { + var test, + name = "<span class='test-name'>" + escapeInnerText( testName ) + "</span>"; if ( arguments.length === 2 ) { callback = expected; @@ -285,180 +326,245 @@ var QUnit = { } if ( config.currentModule ) { - name = '<span class="module-name">' + config.currentModule + "</span>: " + name; - } + name = "<span class='module-name'>" + config.currentModule + "</span>: " + name; + } + + test = new Test({ + name: name, + testName: testName, + expected: expected, + async: async, + callback: callback, + module: config.currentModule, + moduleTestEnvironment: config.currentModuleTestEnviroment, + stack: sourceFromStacktrace( 2 ) + }); - if ( !validTest(config.currentModule + ": " + testName) ) { + if ( !validTest( test ) ) { return; } - var test = new Test(name, testName, expected, async, callback); - test.module = config.currentModule; - test.moduleTestEnvironment = config.currentModuleTestEnviroment; test.queue(); }, - /** - * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. - */ - expect: function(asserts) { + // Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. + expect: function( asserts ) { config.current.expected = asserts; }, + start: function( count ) { + config.semaphore -= count || 1; + // don't start until equal number of stop-calls + if ( config.semaphore > 0 ) { + return; + } + // ignore if start is called more often then stop + if ( config.semaphore < 0 ) { + config.semaphore = 0; + } + // A slight delay, to avoid any current callbacks + if ( defined.setTimeout ) { + window.setTimeout(function() { + if ( config.semaphore > 0 ) { + return; + } + if ( config.timeout ) { + clearTimeout( config.timeout ); + } + + config.blocking = false; + process( true ); + }, 13); + } else { + config.blocking = false; + process( true ); + } + }, + + stop: function( count ) { + config.semaphore += count || 1; + config.blocking = true; + + if ( config.testTimeout && defined.setTimeout ) { + clearTimeout( config.timeout ); + config.timeout = window.setTimeout(function() { + QUnit.ok( false, "Test timed out" ); + config.semaphore = 1; + QUnit.start(); + }, config.testTimeout ); + } + } +}; + +// Asssert helpers +// All of these must call either QUnit.push() or manually do: +// - runLoggingCallbacks( "log", .. ); +// - config.current.assertions.push({ .. }); +QUnit.assert = { /** - * Asserts true. + * Asserts rough true-ish result. + * @name ok + * @function * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); */ - ok: function(a, msg) { - if (!config.current) { - throw new Error("ok() assertion outside test context, was " + sourceFromStacktrace(2)); + ok: function( result, msg ) { + if ( !config.current ) { + throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) ); } - a = !!a; - var details = { - result: a, - message: msg - }; - msg = escapeInnerText(msg); - runLoggingCallbacks( 'log', QUnit, details ); + result = !!result; + + var source, + details = { + result: result, + message: msg + }; + + msg = escapeInnerText( msg || (result ? "okay" : "failed" ) ); + msg = "<span class='test-message'>" + msg + "</span>"; + + if ( !result ) { + source = sourceFromStacktrace( 2 ); + if ( source ) { + details.source = source; + msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr></table>"; + } + } + runLoggingCallbacks( "log", QUnit, details ); config.current.assertions.push({ - result: a, + result: result, message: msg }); }, /** - * Checks that the first two arguments are equal, with an optional message. + * Assert that the first two arguments are equal, with an optional message. * Prints out both actual and expected values. - * - * Prefered to ok( actual == expected, message ) - * - * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); - * - * @param Object actual - * @param Object expected - * @param String message (optional) + * @name equal + * @function + * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" ); */ - equal: function(actual, expected, message) { - QUnit.push(expected == actual, actual, expected, message); + equal: function( actual, expected, message ) { + QUnit.push( expected == actual, actual, expected, message ); }, - notEqual: function(actual, expected, message) { - QUnit.push(expected != actual, actual, expected, message); + /** + * @name notEqual + * @function + */ + notEqual: function( actual, expected, message ) { + QUnit.push( expected != actual, actual, expected, message ); }, - deepEqual: function(actual, expected, message) { - QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); + /** + * @name deepEqual + * @function + */ + deepEqual: function( actual, expected, message ) { + QUnit.push( QUnit.equiv(actual, expected), actual, expected, message ); }, - notDeepEqual: function(actual, expected, message) { - QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); + /** + * @name notDeepEqual + * @function + */ + notDeepEqual: function( actual, expected, message ) { + QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message ); }, - strictEqual: function(actual, expected, message) { - QUnit.push(expected === actual, actual, expected, message); + /** + * @name strictEqual + * @function + */ + strictEqual: function( actual, expected, message ) { + QUnit.push( expected === actual, actual, expected, message ); }, - notStrictEqual: function(actual, expected, message) { - QUnit.push(expected !== actual, actual, expected, message); + /** + * @name notStrictEqual + * @function + */ + notStrictEqual: function( actual, expected, message ) { + QUnit.push( expected !== actual, actual, expected, message ); }, - raises: function(block, expected, message) { - var actual, ok = false; + throws: function( block, expected, message ) { + var actual, + ok = false; - if (typeof expected === 'string') { + // 'expected' is optional + if ( typeof expected === "string" ) { message = expected; expected = null; } + config.current.ignoreGlobalErrors = true; try { - block(); + block.call( config.current.testEnvironment ); } catch (e) { actual = e; } + config.current.ignoreGlobalErrors = false; - if (actual) { + if ( actual ) { // we don't want to validate thrown error - if (!expected) { + if ( !expected ) { ok = true; // expected is a regexp - } else if (QUnit.objectType(expected) === "regexp") { - ok = expected.test(actual); + } else if ( QUnit.objectType( expected ) === "regexp" ) { + ok = expected.test( actual ); // expected is a constructor - } else if (actual instanceof expected) { + } else if ( actual instanceof expected ) { ok = true; // expected is a validation function which returns true is validation passed - } else if (expected.call({}, actual) === true) { + } else if ( expected.call( {}, actual ) === true ) { ok = true; } - } - - QUnit.ok(ok, message); - }, - start: function(count) { - config.semaphore -= count || 1; - if (config.semaphore > 0) { - // don't start until equal number of stop-calls - return; - } - if (config.semaphore < 0) { - // ignore if start is called more often then stop - config.semaphore = 0; - } - // A slight delay, to avoid any current callbacks - if ( defined.setTimeout ) { - window.setTimeout(function() { - if (config.semaphore > 0) { - return; - } - if ( config.timeout ) { - clearTimeout(config.timeout); - } - - config.blocking = false; - process(true); - }, 13); + QUnit.push( ok, actual, null, message ); } else { - config.blocking = false; - process(true); - } - }, - - stop: function(count) { - config.semaphore += count || 1; - config.blocking = true; - - if ( config.testTimeout && defined.setTimeout ) { - clearTimeout(config.timeout); - config.timeout = window.setTimeout(function() { - QUnit.ok( false, "Test timed out" ); - config.semaphore = 1; - QUnit.start(); - }, config.testTimeout); + QUnit.pushFailure( message, null, 'No exception was thrown.' ); } } }; -//We want access to the constructor's prototype -(function() { - function F(){}; - F.prototype = QUnit; - QUnit = new F(); - //Make F QUnit's constructor so that we can add to the prototype later - QUnit.constructor = F; -})(); +/** + * @deprecate since 1.8.0 + * Kept assertion helpers in root for backwards compatibility + */ +extend( QUnit, QUnit.assert ); + +/** + * @deprecated since 1.9.0 + * Kept global "raises()" for backwards compatibility + */ +QUnit.raises = QUnit.assert.throws; -// deprecated; still export them to window to provide clear error messages -// next step: remove entirely +/** + * @deprecated since 1.0.0, replaced with error pushes since 1.3.0 + * Kept to avoid TypeErrors for undefined methods. + */ QUnit.equals = function() { - throw new Error("QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead"); + QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" ); }; QUnit.same = function() { - throw new Error("QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead"); + QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" ); }; -// Maintain internal state -var config = { +// We want access to the constructor's prototype +(function() { + function F() {} + F.prototype = QUnit; + QUnit = new F(); + // Make F QUnit's constructor so that we can add to the prototype later + QUnit.constructor = F; +}()); + +/** + * Config object: Maintain internal state + * Later exposed as QUnit.config + * `config` initialized at top of scope + */ +config = { // The queue of tests to run queue: [], @@ -476,9 +582,25 @@ var config = { // by default, modify document.title when suite is done altertitle: true, - urlConfig: ['noglobals', 'notrycatch'], + // when enabled, all tests must call expect() + requireExpects: false, - //logging callback queues + // add checkboxes that are persisted in the query-string + // when enabled, the id is set to `true` as a `QUnit.config` property + urlConfig: [ + { + id: "noglobals", + label: "Check for Globals", + tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings." + }, + { + id: "notrycatch", + label: "No try-catch", + tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings." + } + ], + + // logging callback queues begin: [], done: [], log: [], @@ -488,16 +610,17 @@ var config = { moduleDone: [] }; -// Load paramaters +// Initialize more QUnit.config and QUnit.urlParams (function() { - var location = window.location || { search: "", protocol: "file:" }, + var i, + location = window.location || { search: "", protocol: "file:" }, params = location.search.slice( 1 ).split( "&" ), length = params.length, urlParams = {}, current; if ( params[ 0 ] ) { - for ( var i = 0; i < length; i++ ) { + for ( i = 0; i < length; i++ ) { current = params[ i ].split( "=" ); current[ 0 ] = decodeURIComponent( current[ 0 ] ); // allow just a key to turn on a flag, e.g., test.html?noglobals @@ -507,31 +630,39 @@ var config = { } QUnit.urlParams = urlParams; + + // String search anywhere in moduleName+testName config.filter = urlParams.filter; + // Exact match of the module name + config.module = urlParams.module; + + config.testNumber = parseInt( urlParams.testNumber, 10 ) || null; + // Figure out if we're running the tests from a server or not - QUnit.isLocal = !!(location.protocol === 'file:'); -})(); + QUnit.isLocal = location.protocol === "file:"; +}()); + +// Export global variables, unless an 'exports' object exists, +// in that case we assume we're in CommonJS (dealt with on the bottom of the script) +if ( typeof exports === "undefined" ) { + extend( window, QUnit ); -// Expose the API as global variables, unless an 'exports' -// object exists, in that case we assume we're in CommonJS -if ( typeof exports === "undefined" || typeof require === "undefined" ) { - extend(window, QUnit); + // Expose QUnit object window.QUnit = QUnit; -} else { - module.exports = QUnit; } -// define these after exposing globals to keep them in these QUnit namespace only -extend(QUnit, { +// Extend QUnit object, +// these after set here because they should not be exposed as global functions +extend( QUnit, { config: config, // Initialize the configuration options init: function() { - extend(config, { + extend( config, { stats: { all: 0, bad: 0 }, moduleStats: { all: 0, bad: 0 }, - started: +new Date, + started: +new Date(), updateRate: 1000, blocking: false, autostart: true, @@ -541,19 +672,21 @@ extend(QUnit, { semaphore: 0 }); - var qunit = id( "qunit" ); + var tests, banner, result, + qunit = id( "qunit" ); + if ( qunit ) { qunit.innerHTML = - '<h1 id="qunit-header">' + escapeInnerText( document.title ) + '</h1>' + - '<h2 id="qunit-banner"></h2>' + - '<div id="qunit-testrunner-toolbar"></div>' + - '<h2 id="qunit-userAgent"></h2>' + - '<ol id="qunit-tests"></ol>'; + "<h1 id='qunit-header'>" + escapeInnerText( document.title ) + "</h1>" + + "<h2 id='qunit-banner'></h2>" + + "<div id='qunit-testrunner-toolbar'></div>" + + "<h2 id='qunit-userAgent'></h2>" + + "<ol id='qunit-tests'></ol>"; } - var tests = id( "qunit-tests" ), - banner = id( "qunit-banner" ), - result = id( "qunit-testresult" ); + tests = id( "qunit-tests" ); + banner = id( "qunit-banner" ); + result = id( "qunit-testresult" ); if ( tests ) { tests.innerHTML = ""; @@ -572,43 +705,36 @@ extend(QUnit, { result.id = "qunit-testresult"; result.className = "result"; tests.parentNode.insertBefore( result, tests ); - result.innerHTML = 'Running...<br/> '; + result.innerHTML = "Running...<br/> "; } }, - /** - * Resets the test setup. Useful for tests that modify the DOM. - * - * If jQuery is available, uses jQuery's replaceWith(), otherwise use replaceChild - */ + // Resets the test setup. Useful for tests that modify the DOM. + // If jQuery is available, uses jQuery's html(), otherwise just innerHTML. reset: function() { - var main = id( 'qunit-fixture' ); - if ( main ) { - if ( window.jQuery ) { - jQuery( main ).replaceWith( config.fixture.cloneNode(true) ); - } else { - main.parentNode.replaceChild(config.fixture.cloneNode(true), main); + var fixture; + + if ( window.jQuery ) { + jQuery( "#qunit-fixture" ).html( config.fixture ); + } else { + fixture = id( "qunit-fixture" ); + if ( fixture ) { + fixture.innerHTML = config.fixture; } } }, - /** - * Trigger an event on an element. - * - * @example triggerEvent( document.body, "click" ); - * - * @param DOMElement elem - * @param String type - */ + // Trigger an event on an element. + // @example triggerEvent( document.body, "click" ); triggerEvent: function( elem, type, event ) { if ( document.createEvent ) { - event = document.createEvent("MouseEvents"); + event = document.createEvent( "MouseEvents" ); event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null); - elem.dispatchEvent( event ); + elem.dispatchEvent( event ); } else if ( elem.fireEvent ) { - elem.fireEvent("on"+type); + elem.fireEvent( "on" + type ); } }, @@ -618,69 +744,74 @@ extend(QUnit, { }, objectType: function( obj ) { - if (typeof obj === "undefined") { + if ( typeof obj === "undefined" ) { return "undefined"; - // consider: typeof null === object } - if (obj === null) { + if ( obj === null ) { return "null"; } - var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || ''; + var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || ""; - switch (type) { - case 'Number': - if (isNaN(obj)) { - return "nan"; - } else { - return "number"; - } - case 'String': - case 'Boolean': - case 'Array': - case 'Date': - case 'RegExp': - case 'Function': - return type.toLowerCase(); - } - if (typeof obj === "object") { - return "object"; + switch ( type ) { + case "Number": + if ( isNaN(obj) ) { + return "nan"; + } + return "number"; + case "String": + case "Boolean": + case "Array": + case "Date": + case "RegExp": + case "Function": + return type.toLowerCase(); + } + if ( typeof obj === "object" ) { + return "object"; } return undefined; }, - push: function(result, actual, expected, message) { - if (!config.current) { - throw new Error("assertion outside test context, was " + sourceFromStacktrace()); + push: function( result, actual, expected, message ) { + if ( !config.current ) { + throw new Error( "assertion outside test context, was " + sourceFromStacktrace() ); } - var details = { - result: result, - message: message, - actual: actual, - expected: expected - }; - message = escapeInnerText(message) || (result ? "okay" : "failed"); - message = '<span class="test-message">' + message + "</span>"; - var output = message; - if (!result) { - expected = escapeInnerText(QUnit.jsDump.parse(expected)); - actual = escapeInnerText(QUnit.jsDump.parse(actual)); - output += '<table><tr class="test-expected"><th>Expected: </th><td><pre>' + expected + '</pre></td></tr>'; - if (actual != expected) { - output += '<tr class="test-actual"><th>Result: </th><td><pre>' + actual + '</pre></td></tr>'; - output += '<tr class="test-diff"><th>Diff: </th><td><pre>' + QUnit.diff(expected, actual) +'</pre></td></tr>'; + var output, source, + details = { + result: result, + message: message, + actual: actual, + expected: expected + }; + + message = escapeInnerText( message ) || ( result ? "okay" : "failed" ); + message = "<span class='test-message'>" + message + "</span>"; + output = message; + + if ( !result ) { + expected = escapeInnerText( QUnit.jsDump.parse(expected) ); + actual = escapeInnerText( QUnit.jsDump.parse(actual) ); + output += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected + "</pre></td></tr>"; + + if ( actual != expected ) { + output += "<tr class='test-actual'><th>Result: </th><td><pre>" + actual + "</pre></td></tr>"; + output += "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>"; } - var source = sourceFromStacktrace(); - if (source) { + + source = sourceFromStacktrace(); + + if ( source ) { details.source = source; - output += '<tr class="test-source"><th>Source: </th><td><pre>' + escapeInnerText(source) + '</pre></td></tr>'; + output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>"; } + output += "</table>"; } - runLoggingCallbacks( 'log', QUnit, details ); + runLoggingCallbacks( "log", QUnit, details ); config.current.assertions.push({ result: !!result, @@ -688,10 +819,47 @@ extend(QUnit, { }); }, + pushFailure: function( message, source, actual ) { + if ( !config.current ) { + throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) ); + } + + var output, + details = { + result: false, + message: message + }; + + message = escapeInnerText( message ) || "error"; + message = "<span class='test-message'>" + message + "</span>"; + output = message; + + output += "<table>"; + + if ( actual ) { + output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeInnerText( actual ) + "</pre></td></tr>"; + } + + if ( source ) { + details.source = source; + output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>"; + } + + output += "</table>"; + + runLoggingCallbacks( "log", QUnit, details ); + + config.current.assertions.push({ + result: false, + message: output + }); + }, + url: function( params ) { params = extend( extend( {}, QUnit.urlParams ), params ); - var querystring = "?", - key; + var key, + querystring = "?"; + for ( key in params ) { if ( !hasOwn.call( params, key ) ) { continue; @@ -705,27 +873,39 @@ extend(QUnit, { extend: extend, id: id, addEvent: addEvent + // load, equiv, jsDump, diff: Attached later }); -//QUnit.constructor is set to the empty F() above so that we can add to it's prototype later -//Doing this allows us to tell if the following methods have been overwritten on the actual -//QUnit object, which is a deprecated way of using the callbacks. -extend(QUnit.constructor.prototype, { +/** + * @deprecated: Created for backwards compatibility with test runner that set the hook function + * into QUnit.{hook}, instead of invoking it and passing the hook function. + * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here. + * Doing this allows us to tell if the following methods have been overwritten on the actual + * QUnit object. + */ +extend( QUnit.constructor.prototype, { + // Logging callbacks; all receive a single argument with the listed properties // run test/logs.html for any related changes - begin: registerLoggingCallback('begin'), + begin: registerLoggingCallback( "begin" ), + // done: { failed, passed, total, runtime } - done: registerLoggingCallback('done'), + done: registerLoggingCallback( "done" ), + // log: { result, actual, expected, message } - log: registerLoggingCallback('log'), + log: registerLoggingCallback( "log" ), + // testStart: { name } - testStart: registerLoggingCallback('testStart'), + testStart: registerLoggingCallback( "testStart" ), + // testDone: { name, failed, passed, total } - testDone: registerLoggingCallback('testDone'), + testDone: registerLoggingCallback( "testDone" ), + // moduleStart: { name } - moduleStart: registerLoggingCallback('moduleStart'), + moduleStart: registerLoggingCallback( "moduleStart" ), + // moduleDone: { name, failed, passed, total } - moduleDone: registerLoggingCallback('moduleDone') + moduleDone: registerLoggingCallback( "moduleDone" ) }); if ( typeof document === "undefined" || document.readyState === "complete" ) { @@ -733,90 +913,140 @@ if ( typeof document === "undefined" || document.readyState === "complete" ) { } QUnit.load = function() { - runLoggingCallbacks( 'begin', QUnit, {} ); + runLoggingCallbacks( "begin", QUnit, {} ); // Initialize the config, saving the execution queue - var oldconfig = extend({}, config); + var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes, + urlConfigHtml = "", + oldconfig = extend( {}, config ); + QUnit.init(); extend(config, oldconfig); config.blocking = false; - var urlConfigHtml = '', len = config.urlConfig.length; - for ( var i = 0, val; i < len, val = config.urlConfig[i]; i++ ) { - config[val] = QUnit.urlParams[val]; - urlConfigHtml += '<label><input name="' + val + '" type="checkbox"' + ( config[val] ? ' checked="checked"' : '' ) + '>' + val + '</label>'; + len = config.urlConfig.length; + + for ( i = 0; i < len; i++ ) { + val = config.urlConfig[i]; + if ( typeof val === "string" ) { + val = { + id: val, + label: val, + tooltip: "[no tooltip available]" + }; + } + config[ val.id ] = QUnit.urlParams[ val.id ]; + urlConfigHtml += "<input id='qunit-urlconfig-" + val.id + "' name='" + val.id + "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) + " title='" + val.tooltip + "'><label for='qunit-urlconfig-" + val.id + "' title='" + val.tooltip + "'>" + val.label + "</label>"; } - var userAgent = id("qunit-userAgent"); + // `userAgent` initialized at top of scope + userAgent = id( "qunit-userAgent" ); if ( userAgent ) { userAgent.innerHTML = navigator.userAgent; } - var banner = id("qunit-header"); + + // `banner` initialized at top of scope + banner = id( "qunit-header" ); if ( banner ) { - banner.innerHTML = '<a href="' + QUnit.url({ filter: undefined }) + '"> ' + banner.innerHTML + '</a> ' + urlConfigHtml; - addEvent( banner, "change", function( event ) { - var params = {}; - params[ event.target.name ] = event.target.checked ? true : undefined; - window.location = QUnit.url( params ); - }); + banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + "'>" + banner.innerHTML + "</a> "; } - var toolbar = id("qunit-testrunner-toolbar"); + // `toolbar` initialized at top of scope + toolbar = id( "qunit-testrunner-toolbar" ); if ( toolbar ) { - var filter = document.createElement("input"); + // `filter` initialized at top of scope + filter = document.createElement( "input" ); filter.type = "checkbox"; filter.id = "qunit-filter-pass"; + addEvent( filter, "click", function() { - var ol = document.getElementById("qunit-tests"); + var tmp, + ol = document.getElementById( "qunit-tests" ); + if ( filter.checked ) { ol.className = ol.className + " hidepass"; } else { - var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; - ol.className = tmp.replace(/ hidepass /, " "); + tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; + ol.className = tmp.replace( / hidepass /, " " ); } if ( defined.sessionStorage ) { if (filter.checked) { - sessionStorage.setItem("qunit-filter-passed-tests", "true"); + sessionStorage.setItem( "qunit-filter-passed-tests", "true" ); } else { - sessionStorage.removeItem("qunit-filter-passed-tests"); + sessionStorage.removeItem( "qunit-filter-passed-tests" ); } } }); - if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { + + if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) { filter.checked = true; - var ol = document.getElementById("qunit-tests"); + // `ol` initialized at top of scope + ol = document.getElementById( "qunit-tests" ); ol.className = ol.className + " hidepass"; } toolbar.appendChild( filter ); - var label = document.createElement("label"); - label.setAttribute("for", "qunit-filter-pass"); + // `label` initialized at top of scope + label = document.createElement( "label" ); + label.setAttribute( "for", "qunit-filter-pass" ); + label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." ); label.innerHTML = "Hide passed tests"; toolbar.appendChild( label ); + + urlConfigCheckboxes = document.createElement( 'span' ); + urlConfigCheckboxes.innerHTML = urlConfigHtml; + addEvent( urlConfigCheckboxes, "change", function( event ) { + var params = {}; + params[ event.target.name ] = event.target.checked ? true : undefined; + window.location = QUnit.url( params ); + }); + toolbar.appendChild( urlConfigCheckboxes ); } - var main = id('qunit-fixture'); + // `main` initialized at top of scope + main = id( "qunit-fixture" ); if ( main ) { - config.fixture = main.cloneNode(true); + config.fixture = main.innerHTML; } - if (config.autostart) { + if ( config.autostart ) { QUnit.start(); } }; -addEvent(window, "load", QUnit.load); +addEvent( window, "load", QUnit.load ); -// addEvent(window, "error") gives us a useless event object -window.onerror = function( message, file, line ) { - if ( QUnit.config.current ) { - ok( false, message + ", " + file + ":" + line ); - } else { - test( "global failure", function() { - ok( false, message + ", " + file + ":" + line ); - }); +// `onErrorFnPrev` initialized at top of scope +// Preserve other handlers +onErrorFnPrev = window.onerror; + +// Cover uncaught exceptions +// Returning true will surpress the default browser handler, +// returning false will let it run. +window.onerror = function ( error, filePath, linerNr ) { + var ret = false; + if ( onErrorFnPrev ) { + ret = onErrorFnPrev( error, filePath, linerNr ); } + + // Treat return value as window.onerror itself does, + // Only do our handling if not surpressed. + if ( ret !== true ) { + if ( QUnit.config.current ) { + if ( QUnit.config.current.ignoreGlobalErrors ) { + return true; + } + QUnit.pushFailure( error, filePath + ":" + linerNr ); + } else { + QUnit.test( "global failure", function() { + QUnit.pushFailure( error, filePath + ":" + linerNr ); + }); + } + return false; + } + + return ret; }; function done() { @@ -824,33 +1054,34 @@ function done() { // Log the last module results if ( config.currentModule ) { - runLoggingCallbacks( 'moduleDone', QUnit, { + runLoggingCallbacks( "moduleDone", QUnit, { name: config.currentModule, failed: config.moduleStats.bad, passed: config.moduleStats.all - config.moduleStats.bad, total: config.moduleStats.all - } ); + }); } - var banner = id("qunit-banner"), - tests = id("qunit-tests"), - runtime = +new Date - config.started, + var i, key, + banner = id( "qunit-banner" ), + tests = id( "qunit-tests" ), + runtime = +new Date() - config.started, passed = config.stats.all - config.stats.bad, html = [ - 'Tests completed in ', + "Tests completed in ", runtime, - ' milliseconds.<br/>', - '<span class="passed">', + " milliseconds.<br/>", + "<span class='passed'>", passed, - '</span> tests of <span class="total">', + "</span> tests of <span class='total'>", config.stats.all, - '</span> passed, <span class="failed">', + "</span> passed, <span class='failed'>", config.stats.bad, - '</span> failed.' - ].join(''); + "</span> failed." + ].join( "" ); if ( banner ) { - banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); + banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" ); } if ( tests ) { @@ -861,84 +1092,119 @@ function done() { // show ✖ for good, ✔ for bad suite result in title // use escape sequences in case file gets loaded with non-utf-8-charset document.title = [ - (config.stats.bad ? "\u2716" : "\u2714"), - document.title.replace(/^[\u2714\u2716] /i, "") - ].join(" "); + ( config.stats.bad ? "\u2716" : "\u2714" ), + document.title.replace( /^[\u2714\u2716] /i, "" ) + ].join( " " ); } // clear own sessionStorage items if all tests passed if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) { - for (var key in sessionStorage) { - if (sessionStorage.hasOwnProperty(key) && key.indexOf("qunit-") === 0 ) { - sessionStorage.removeItem(key); + // `key` & `i` initialized at top of scope + for ( i = 0; i < sessionStorage.length; i++ ) { + key = sessionStorage.key( i++ ); + if ( key.indexOf( "qunit-test-" ) === 0 ) { + sessionStorage.removeItem( key ); } } } - runLoggingCallbacks( 'done', QUnit, { + runLoggingCallbacks( "done", QUnit, { failed: config.stats.bad, passed: passed, total: config.stats.all, runtime: runtime - } ); + }); } -function validTest( name ) { - var filter = config.filter, - run = false; +/** @return Boolean: true if this test should be ran */ +function validTest( test ) { + var include, + filter = config.filter && config.filter.toLowerCase(), + module = config.module && config.module.toLowerCase(), + fullName = (test.module + ": " + test.testName).toLowerCase(); + + if ( config.testNumber ) { + return test.testNumber === config.testNumber; + } + + if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) { + return false; + } if ( !filter ) { return true; } - var not = filter.charAt( 0 ) === "!"; - if ( not ) { + include = filter.charAt( 0 ) !== "!"; + if ( !include ) { filter = filter.slice( 1 ); } - if ( name.indexOf( filter ) !== -1 ) { - return !not; - } - - if ( not ) { - run = true; + // If the filter matches, we need to honour include + if ( fullName.indexOf( filter ) !== -1 ) { + return include; } - return run; + // Otherwise, do the opposite + return !include; } -// so far supports only Firefox, Chrome and Opera (buggy) -// could be extended in the future to use something like https://github.com/csnover/TraceKit -function sourceFromStacktrace(offset) { - offset = offset || 3; +// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions) +// Later Safari and IE10 are supposed to support error.stack as well +// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack +function extractStacktrace( e, offset ) { + offset = offset === undefined ? 3 : offset; + + var stack, include, i, regex; + + if ( e.stacktrace ) { + // Opera + return e.stacktrace.split( "\n" )[ offset + 3 ]; + } else if ( e.stack ) { + // Firefox, Chrome + stack = e.stack.split( "\n" ); + if (/^error$/i.test( stack[0] ) ) { + stack.shift(); + } + if ( fileName ) { + include = []; + for ( i = offset; i < stack.length; i++ ) { + if ( stack[ i ].indexOf( fileName ) != -1 ) { + break; + } + include.push( stack[ i ] ); + } + if ( include.length ) { + return include.join( "\n" ); + } + } + return stack[ offset ]; + } else if ( e.sourceURL ) { + // Safari, PhantomJS + // hopefully one day Safari provides actual stacktraces + // exclude useless self-reference for generated Error objects + if ( /qunit.js$/.test( e.sourceURL ) ) { + return; + } + // for actual exceptions, this is useful + return e.sourceURL + ":" + e.line; + } +} +function sourceFromStacktrace( offset ) { try { throw new Error(); } catch ( e ) { - if (e.stacktrace) { - // Opera - return e.stacktrace.split("\n")[offset + 3]; - } else if (e.stack) { - // Firefox, Chrome - var stack = e.stack.split("\n"); - if (/^error$/i.test(stack[0])) { - stack.shift(); - } - return stack[offset]; - } else if (e.sourceURL) { - // Safari, PhantomJS - // TODO sourceURL points at the 'throw new Error' line above, useless - //return e.sourceURL + ":" + e.line; - } + return extractStacktrace( e, offset ); } } -function escapeInnerText(s) { - if (!s) { +function escapeInnerText( s ) { + if ( !s ) { return ""; } s = s + ""; - return s.replace(/[\&<>]/g, function(s) { - switch(s) { + return s.replace( /[\&<>]/g, function( s ) { + switch( s ) { case "&": return "&"; case "<": return "<"; case ">": return ">"; @@ -951,11 +1217,14 @@ function synchronize( callback, last ) { config.queue.push( callback ); if ( config.autorun && !config.blocking ) { - process(last); + process( last ); } } function process( last ) { + function next() { + process( last ); + } var start = new Date().getTime(); config.depth = config.depth ? config.depth + 1 : 1; @@ -963,9 +1232,7 @@ function process( last ) { if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) { config.queue.shift()(); } else { - window.setTimeout( function(){ - process( last ); - }, 13 ); + window.setTimeout( next, 13 ); break; } } @@ -980,7 +1247,8 @@ function saveGlobal() { if ( config.noglobals ) { for ( var key in window ) { - if ( !hasOwn.call( window, key ) ) { + // in Opera sometimes DOM element ids show up here, ignore them + if ( !hasOwn.call( window, key ) || /^qunit-test-output/.test( key ) ) { continue; } config.pollution.push( key ); @@ -989,27 +1257,32 @@ function saveGlobal() { } function checkPollution( name ) { - var old = config.pollution; + var newGlobals, + deletedGlobals, + old = config.pollution; + saveGlobal(); - var newGlobals = diff( config.pollution, old ); + newGlobals = diff( config.pollution, old ); if ( newGlobals.length > 0 ) { - ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); + QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") ); } - var deletedGlobals = diff( old, config.pollution ); + deletedGlobals = diff( old, config.pollution ); if ( deletedGlobals.length > 0 ) { - ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); + QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") ); } } // returns a new Array with the elements that are in a but not in b function diff( a, b ) { - var result = a.slice(); - for ( var i = 0; i < result.length; i++ ) { - for ( var j = 0; j < b.length; j++ ) { + var i, j, + result = a.slice(); + + for ( i = 0; i < result.length; i++ ) { + for ( j = 0; j < b.length; j++ ) { if ( result[i] === b[j] ) { - result.splice(i, 1); + result.splice( i, 1 ); i--; break; } @@ -1018,33 +1291,21 @@ function diff( a, b ) { return result; } -function fail(message, exception, callback) { - if ( typeof console !== "undefined" && console.error && console.warn ) { - console.error(message); - console.error(exception); - console.error(exception.stack); - console.warn(callback.toString()); - - } else if ( window.opera && opera.postError ) { - opera.postError(message, exception, callback.toString); - } -} - -function extend(a, b) { +function extend( a, b ) { for ( var prop in b ) { - if ( b[prop] === undefined ) { - delete a[prop]; + if ( b[ prop ] === undefined ) { + delete a[ prop ]; // Avoid "Member not found" error in IE8 caused by setting window.constructor } else if ( prop !== "constructor" || a !== window ) { - a[prop] = b[prop]; + a[ prop ] = b[ prop ]; } } return a; } -function addEvent(elem, type, fn) { +function addEvent( elem, type, fn ) { if ( elem.addEventListener ) { elem.addEventListener( type, fn, false ); } else if ( elem.attachEvent ) { @@ -1054,215 +1315,214 @@ function addEvent(elem, type, fn) { } } -function id(name) { - return !!(typeof document !== "undefined" && document && document.getElementById) && +function id( name ) { + return !!( typeof document !== "undefined" && document && document.getElementById ) && document.getElementById( name ); } -function registerLoggingCallback(key){ - return function(callback){ +function registerLoggingCallback( key ) { + return function( callback ) { config[key].push( callback ); }; } // Supports deprecated method of completely overwriting logging callbacks -function runLoggingCallbacks(key, scope, args) { +function runLoggingCallbacks( key, scope, args ) { //debugger; - var callbacks; - if ( QUnit.hasOwnProperty(key) ) { - QUnit[key].call(scope, args); + var i, callbacks; + if ( QUnit.hasOwnProperty( key ) ) { + QUnit[ key ].call(scope, args ); } else { - callbacks = config[key]; - for( var i = 0; i < callbacks.length; i++ ) { - callbacks[i].call( scope, args ); + callbacks = config[ key ]; + for ( i = 0; i < callbacks.length; i++ ) { + callbacks[ i ].call( scope, args ); } } } // Test for equality any JavaScript type. // Author: Philippe Rathé <prathe@gmail.com> -QUnit.equiv = function () { - - var innerEquiv; // the real equiv function - var callers = []; // stack to decide between skip/abort functions - var parents = []; // stack to avoiding loops from circular referencing +QUnit.equiv = (function() { // Call the o related callback with the given arguments. - function bindCallbacks(o, callbacks, args) { - var prop = QUnit.objectType(o); - if (prop) { - if (QUnit.objectType(callbacks[prop]) === "function") { - return callbacks[prop].apply(callbacks, args); + function bindCallbacks( o, callbacks, args ) { + var prop = QUnit.objectType( o ); + if ( prop ) { + if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) { + return callbacks[ prop ].apply( callbacks, args ); } else { - return callbacks[prop]; // or undefined + return callbacks[ prop ]; // or undefined } } } - var getProto = Object.getPrototypeOf || function (obj) { - return obj.__proto__; - }; - - var callbacks = function () { - - // for string, boolean, number and null - function useStrictEquality(b, a) { - if (b instanceof a.constructor || a instanceof b.constructor) { - // to catch short annotaion VS 'new' annotation of a - // declaration - // e.g. var i = 1; - // var j = new Number(1); - return a == b; - } else { - return a === b; - } - } - - return { - "string" : useStrictEquality, - "boolean" : useStrictEquality, - "number" : useStrictEquality, - "null" : useStrictEquality, - "undefined" : useStrictEquality, - - "nan" : function(b) { - return isNaN(b); - }, - - "date" : function(b, a) { - return QUnit.objectType(b) === "date" - && a.valueOf() === b.valueOf(); - }, - - "regexp" : function(b, a) { - return QUnit.objectType(b) === "regexp" - && a.source === b.source && // the regex itself - a.global === b.global && // and its modifers - // (gmi) ... - a.ignoreCase === b.ignoreCase - && a.multiline === b.multiline; - }, - - // - skip when the property is a method of an instance (OOP) - // - abort otherwise, - // initial === would have catch identical references anyway - "function" : function() { - var caller = callers[callers.length - 1]; - return caller !== Object && typeof caller !== "undefined"; - }, - - "array" : function(b, a) { - var i, j, loop; - var len; - - // b could be an object literal here - if (!(QUnit.objectType(b) === "array")) { - return false; - } + // the real equiv function + var innerEquiv, + // stack to decide between skip/abort functions + callers = [], + // stack to avoiding loops from circular referencing + parents = [], - len = a.length; - if (len !== b.length) { // safe and faster - return false; + getProto = Object.getPrototypeOf || function ( obj ) { + return obj.__proto__; + }, + callbacks = (function () { + + // for string, boolean, number and null + function useStrictEquality( b, a ) { + if ( b instanceof a.constructor || a instanceof b.constructor ) { + // to catch short annotaion VS 'new' annotation of a + // declaration + // e.g. var i = 1; + // var j = new Number(1); + return a == b; + } else { + return a === b; } + } - // track reference to avoid circular references - parents.push(a); - for (i = 0; i < len; i++) { - loop = false; - for (j = 0; j < parents.length; j++) { - if (parents[j] === a[i]) { - loop = true;// dont rewalk array - } - } - if (!loop && !innerEquiv(a[i], b[i])) { - parents.pop(); + return { + "string": useStrictEquality, + "boolean": useStrictEquality, + "number": useStrictEquality, + "null": useStrictEquality, + "undefined": useStrictEquality, + + "nan": function( b ) { + return isNaN( b ); + }, + + "date": function( b, a ) { + return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf(); + }, + + "regexp": function( b, a ) { + return QUnit.objectType( b ) === "regexp" && + // the regex itself + a.source === b.source && + // and its modifers + a.global === b.global && + // (gmi) ... + a.ignoreCase === b.ignoreCase && + a.multiline === b.multiline; + }, + + // - skip when the property is a method of an instance (OOP) + // - abort otherwise, + // initial === would have catch identical references anyway + "function": function() { + var caller = callers[callers.length - 1]; + return caller !== Object && typeof caller !== "undefined"; + }, + + "array": function( b, a ) { + var i, j, len, loop; + + // b could be an object literal here + if ( QUnit.objectType( b ) !== "array" ) { return false; } - } - parents.pop(); - return true; - }, - "object" : function(b, a) { - var i, j, loop; - var eq = true; // unless we can proove it - var aProperties = [], bProperties = []; // collection of - // strings - - // comparing constructors is more strict than using - // instanceof - if (a.constructor !== b.constructor) { - // Allow objects with no prototype to be equivalent to - // objects with Object as their constructor. - if (!((getProto(a) === null && getProto(b) === Object.prototype) || - (getProto(b) === null && getProto(a) === Object.prototype))) - { + len = a.length; + if ( len !== b.length ) { + // safe and faster return false; } - } - // stack constructor before traversing properties - callers.push(a.constructor); - // track reference to avoid circular references - parents.push(a); - - for (i in a) { // be strict: don't ensures hasOwnProperty - // and go deep - loop = false; - for (j = 0; j < parents.length; j++) { - if (parents[j] === a[i]) - loop = true; // don't go down the same path - // twice + // track reference to avoid circular references + parents.push( a ); + for ( i = 0; i < len; i++ ) { + loop = false; + for ( j = 0; j < parents.length; j++ ) { + if ( parents[j] === a[i] ) { + loop = true;// dont rewalk array + } + } + if ( !loop && !innerEquiv(a[i], b[i]) ) { + parents.pop(); + return false; + } + } + parents.pop(); + return true; + }, + + "object": function( b, a ) { + var i, j, loop, + // Default to true + eq = true, + aProperties = [], + bProperties = []; + + // comparing constructors is more strict than using + // instanceof + if ( a.constructor !== b.constructor ) { + // Allow objects with no prototype to be equivalent to + // objects with Object as their constructor. + if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) || + ( getProto(b) === null && getProto(a) === Object.prototype ) ) ) { + return false; + } } - aProperties.push(i); // collect a's properties - if (!loop && !innerEquiv(a[i], b[i])) { - eq = false; - break; + // stack constructor before traversing properties + callers.push( a.constructor ); + // track reference to avoid circular references + parents.push( a ); + + for ( i in a ) { // be strict: don't ensures hasOwnProperty + // and go deep + loop = false; + for ( j = 0; j < parents.length; j++ ) { + if ( parents[j] === a[i] ) { + // don't go down the same path twice + loop = true; + } + } + aProperties.push(i); // collect a's properties + + if (!loop && !innerEquiv( a[i], b[i] ) ) { + eq = false; + break; + } } - } - callers.pop(); // unstack, we are done - parents.pop(); + callers.pop(); // unstack, we are done + parents.pop(); - for (i in b) { - bProperties.push(i); // collect b's properties - } + for ( i in b ) { + bProperties.push( i ); // collect b's properties + } - // Ensures identical properties name - return eq - && innerEquiv(aProperties.sort(), bProperties - .sort()); - } - }; - }(); + // Ensures identical properties name + return eq && innerEquiv( aProperties.sort(), bProperties.sort() ); + } + }; + }()); innerEquiv = function() { // can take multiple arguments - var args = Array.prototype.slice.apply(arguments); - if (args.length < 2) { + var args = [].slice.apply( arguments ); + if ( args.length < 2 ) { return true; // end transition } - return (function(a, b) { - if (a === b) { + return (function( a, b ) { + if ( a === b ) { return true; // catch the most you can - } else if (a === null || b === null || typeof a === "undefined" - || typeof b === "undefined" - || QUnit.objectType(a) !== QUnit.objectType(b)) { + } else if ( a === null || b === null || typeof a === "undefined" || + typeof b === "undefined" || + QUnit.objectType(a) !== QUnit.objectType(b) ) { return false; // don't lose time with error prone cases } else { return bindCallbacks(a, callbacks, [ b, a ]); } // apply transition with (1..n) arguments - })(args[0], args[1]) - && arguments.callee.apply(this, args.splice(1, - args.length - 1)); + }( args[0], args[1] ) && arguments.callee.apply( this, args.splice(1, args.length - 1 )) ); }; return innerEquiv; - -}(); +}()); /** * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | @@ -1276,188 +1536,215 @@ QUnit.equiv = function () { */ QUnit.jsDump = (function() { function quote( str ) { - return '"' + str.toString().replace(/"/g, '\\"') + '"'; - }; + return '"' + str.toString().replace( /"/g, '\\"' ) + '"'; + } function literal( o ) { - return o + ''; - }; + return o + ""; + } function join( pre, arr, post ) { var s = jsDump.separator(), base = jsDump.indent(), inner = jsDump.indent(1); - if ( arr.join ) - arr = arr.join( ',' + s + inner ); - if ( !arr ) + if ( arr.join ) { + arr = arr.join( "," + s + inner ); + } + if ( !arr ) { return pre + post; + } return [ pre, inner + arr, base + post ].join(s); - }; + } function array( arr, stack ) { - var i = arr.length, ret = Array(i); + var i = arr.length, ret = new Array(i); this.up(); - while ( i-- ) + while ( i-- ) { ret[i] = this.parse( arr[i] , undefined , stack); + } this.down(); - return join( '[', ret, ']' ); - }; + return join( "[", ret, "]" ); + } - var reName = /^function (\w+)/; + var reName = /^function (\w+)/, + jsDump = { + parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance + stack = stack || [ ]; + var inStack, res, + parser = this.parsers[ type || this.typeOf(obj) ]; - var jsDump = { - parse:function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance - stack = stack || [ ]; - var parser = this.parsers[ type || this.typeOf(obj) ]; - type = typeof parser; - var inStack = inArray(obj, stack); - if (inStack != -1) { - return 'recursion('+(inStack - stack.length)+')'; - } - //else - if (type == 'function') { - stack.push(obj); - var res = parser.call( this, obj, stack ); + type = typeof parser; + inStack = inArray( obj, stack ); + + if ( inStack != -1 ) { + return "recursion(" + (inStack - stack.length) + ")"; + } + //else + if ( type == "function" ) { + stack.push( obj ); + res = parser.call( this, obj, stack ); stack.pop(); return res; - } - // else - return (type == 'string') ? parser : this.parsers.error; - }, - typeOf:function( obj ) { - var type; - if ( obj === null ) { - type = "null"; - } else if (typeof obj === "undefined") { - type = "undefined"; - } else if (QUnit.is("RegExp", obj)) { - type = "regexp"; - } else if (QUnit.is("Date", obj)) { - type = "date"; - } else if (QUnit.is("Function", obj)) { - type = "function"; - } else if (typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined") { - type = "window"; - } else if (obj.nodeType === 9) { - type = "document"; - } else if (obj.nodeType) { - type = "node"; - } else if ( - // native arrays - toString.call( obj ) === "[object Array]" || - // NodeList objects - ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) - ) { - type = "array"; - } else { - type = typeof obj; - } - return type; - }, - separator:function() { - return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? ' ' : ' '; - }, - indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing - if ( !this.multiline ) - return ''; - var chr = this.indentChar; - if ( this.HTML ) - chr = chr.replace(/\t/g,' ').replace(/ /g,' '); - return Array( this._depth_ + (extra||0) ).join(chr); - }, - up:function( a ) { - this._depth_ += a || 1; - }, - down:function( a ) { - this._depth_ -= a || 1; - }, - setParser:function( name, parser ) { - this.parsers[name] = parser; - }, - // The next 3 are exposed so you can use them - quote:quote, - literal:literal, - join:join, - // - _depth_: 1, - // This is the list of parsers, to modify them, use jsDump.setParser - parsers:{ - window: '[Window]', - document: '[Document]', - error:'[ERROR]', //when no parser is found, shouldn't happen - unknown: '[Unknown]', - 'null':'null', - 'undefined':'undefined', - 'function':function( fn ) { - var ret = 'function', - name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE - if ( name ) - ret += ' ' + name; - ret += '('; - - ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); - return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); + } + // else + return ( type == "string" ) ? parser : this.parsers.error; }, - array: array, - nodelist: array, - arguments: array, - object:function( map, stack ) { - var ret = [ ]; - QUnit.jsDump.up(); - for ( var key in map ) { - var val = map[key]; - ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(val, undefined, stack)); + typeOf: function( obj ) { + var type; + if ( obj === null ) { + type = "null"; + } else if ( typeof obj === "undefined" ) { + type = "undefined"; + } else if ( QUnit.is( "regexp", obj) ) { + type = "regexp"; + } else if ( QUnit.is( "date", obj) ) { + type = "date"; + } else if ( QUnit.is( "function", obj) ) { + type = "function"; + } else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) { + type = "window"; + } else if ( obj.nodeType === 9 ) { + type = "document"; + } else if ( obj.nodeType ) { + type = "node"; + } else if ( + // native arrays + toString.call( obj ) === "[object Array]" || + // NodeList objects + ( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) ) + ) { + type = "array"; + } else { + type = typeof obj; } - QUnit.jsDump.down(); - return join( '{', ret, '}' ); + return type; }, - node:function( node ) { - var open = QUnit.jsDump.HTML ? '<' : '<', - close = QUnit.jsDump.HTML ? '>' : '>'; - - var tag = node.nodeName.toLowerCase(), - ret = open + tag; - - for ( var a in QUnit.jsDump.DOMAttrs ) { - var val = node[QUnit.jsDump.DOMAttrs[a]]; - if ( val ) - ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); + separator: function() { + return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? " " : " "; + }, + indent: function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing + if ( !this.multiline ) { + return ""; + } + var chr = this.indentChar; + if ( this.HTML ) { + chr = chr.replace( /\t/g, " " ).replace( / /g, " " ); } - return ret + close + open + '/' + tag + close; + return new Array( this._depth_ + (extra||0) ).join(chr); }, - functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function - var l = fn.length; - if ( !l ) return ''; - - var args = Array(l); - while ( l-- ) - args[l] = String.fromCharCode(97+l);//97 is 'a' - return ' ' + args.join(', ') + ' '; + up: function( a ) { + this._depth_ += a || 1; }, - key:quote, //object calls it internally, the key part of an item in a map - functionCode:'[code]', //function calls it internally, it's the content of the function - attribute:quote, //node calls it internally, it's an html attribute value - string:quote, - date:quote, - regexp:literal, //regex - number:literal, - 'boolean':literal - }, - DOMAttrs:{//attributes to dump from nodes, name=>realName - id:'id', - name:'name', - 'class':'className' - }, - HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) - indentChar:' ',//indentation unit - multiline:true //if true, items in a collection, are separated by a \n, else just a space. - }; + down: function( a ) { + this._depth_ -= a || 1; + }, + setParser: function( name, parser ) { + this.parsers[name] = parser; + }, + // The next 3 are exposed so you can use them + quote: quote, + literal: literal, + join: join, + // + _depth_: 1, + // This is the list of parsers, to modify them, use jsDump.setParser + parsers: { + window: "[Window]", + document: "[Document]", + error: "[ERROR]", //when no parser is found, shouldn"t happen + unknown: "[Unknown]", + "null": "null", + "undefined": "undefined", + "function": function( fn ) { + var ret = "function", + name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];//functions never have name in IE + + if ( name ) { + ret += " " + name; + } + ret += "( "; + + ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" ); + return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" ); + }, + array: array, + nodelist: array, + "arguments": array, + object: function( map, stack ) { + var ret = [ ], keys, key, val, i; + QUnit.jsDump.up(); + if ( Object.keys ) { + keys = Object.keys( map ); + } else { + keys = []; + for ( key in map ) { + keys.push( key ); + } + } + keys.sort(); + for ( i = 0; i < keys.length; i++ ) { + key = keys[ i ]; + val = map[ key ]; + ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) ); + } + QUnit.jsDump.down(); + return join( "{", ret, "}" ); + }, + node: function( node ) { + var a, val, + open = QUnit.jsDump.HTML ? "<" : "<", + close = QUnit.jsDump.HTML ? ">" : ">", + tag = node.nodeName.toLowerCase(), + ret = open + tag; + + for ( a in QUnit.jsDump.DOMAttrs ) { + val = node[ QUnit.jsDump.DOMAttrs[a] ]; + if ( val ) { + ret += " " + a + "=" + QUnit.jsDump.parse( val, "attribute" ); + } + } + return ret + close + open + "/" + tag + close; + }, + functionArgs: function( fn ) {//function calls it internally, it's the arguments part of the function + var args, + l = fn.length; + + if ( !l ) { + return ""; + } + + args = new Array(l); + while ( l-- ) { + args[l] = String.fromCharCode(97+l);//97 is 'a' + } + return " " + args.join( ", " ) + " "; + }, + key: quote, //object calls it internally, the key part of an item in a map + functionCode: "[code]", //function calls it internally, it's the content of the function + attribute: quote, //node calls it internally, it's an html attribute value + string: quote, + date: quote, + regexp: literal, //regex + number: literal, + "boolean": literal + }, + DOMAttrs: { + //attributes to dump from nodes, name=>realName + id: "id", + name: "name", + "class": "className" + }, + HTML: false,//if true, entities are escaped ( <, >, \t, space and \n ) + indentChar: " ",//indentation unit + multiline: true //if true, items in a collection, are separated by a \n, else just a space. + }; return jsDump; -})(); +}()); // from Sizzle.js function getText( elems ) { - var ret = "", elem; + var i, elem, + ret = ""; - for ( var i = 0; elems[i]; i++ ) { + for ( i = 0; elems[i]; i++ ) { elem = elems[i]; // Get the text from text nodes and CDATA nodes @@ -1471,9 +1758,9 @@ function getText( elems ) { } return ret; -}; +} -//from jquery.js +// from jquery.js function inArray( elem, array ) { if ( array.indexOf ) { return array.indexOf( elem ); @@ -1500,70 +1787,75 @@ function inArray( elem, array ) { * * Usage: QUnit.diff(expected, actual) * - * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over" + * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over" */ QUnit.diff = (function() { - function diff(o, n) { - var ns = {}; - var os = {}; - - for (var i = 0; i < n.length; i++) { - if (ns[n[i]] == null) - ns[n[i]] = { + function diff( o, n ) { + var i, + ns = {}, + os = {}; + + for ( i = 0; i < n.length; i++ ) { + if ( ns[ n[i] ] == null ) { + ns[ n[i] ] = { rows: [], o: null }; - ns[n[i]].rows.push(i); + } + ns[ n[i] ].rows.push( i ); } - for (var i = 0; i < o.length; i++) { - if (os[o[i]] == null) - os[o[i]] = { + for ( i = 0; i < o.length; i++ ) { + if ( os[ o[i] ] == null ) { + os[ o[i] ] = { rows: [], n: null }; - os[o[i]].rows.push(i); + } + os[ o[i] ].rows.push( i ); } - for (var i in ns) { + for ( i in ns ) { if ( !hasOwn.call( ns, i ) ) { continue; } - if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { - n[ns[i].rows[0]] = { - text: n[ns[i].rows[0]], + if ( ns[i].rows.length == 1 && typeof os[i] != "undefined" && os[i].rows.length == 1 ) { + n[ ns[i].rows[0] ] = { + text: n[ ns[i].rows[0] ], row: os[i].rows[0] }; - o[os[i].rows[0]] = { - text: o[os[i].rows[0]], + o[ os[i].rows[0] ] = { + text: o[ os[i].rows[0] ], row: ns[i].rows[0] }; } } - for (var i = 0; i < n.length - 1; i++) { - if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && - n[i + 1] == o[n[i].row + 1]) { - n[i + 1] = { - text: n[i + 1], + for ( i = 0; i < n.length - 1; i++ ) { + if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null && + n[ i + 1 ] == o[ n[i].row + 1 ] ) { + + n[ i + 1 ] = { + text: n[ i + 1 ], row: n[i].row + 1 }; - o[n[i].row + 1] = { - text: o[n[i].row + 1], + o[ n[i].row + 1 ] = { + text: o[ n[i].row + 1 ], row: i + 1 }; } } - for (var i = n.length - 1; i > 0; i--) { - if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && - n[i - 1] == o[n[i].row - 1]) { - n[i - 1] = { - text: n[i - 1], + for ( i = n.length - 1; i > 0; i-- ) { + if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null && + n[ i - 1 ] == o[ n[i].row - 1 ]) { + + n[ i - 1 ] = { + text: n[ i - 1 ], row: n[i].row - 1 }; - o[n[i].row - 1] = { - text: o[n[i].row - 1], + o[ n[i].row - 1 ] = { + text: o[ n[i].row - 1 ], row: i - 1 }; } @@ -1575,49 +1867,52 @@ QUnit.diff = (function() { }; } - return function(o, n) { - o = o.replace(/\s+$/, ''); - n = n.replace(/\s+$/, ''); - var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); + return function( o, n ) { + o = o.replace( /\s+$/, "" ); + n = n.replace( /\s+$/, "" ); - var str = ""; + var i, pre, + str = "", + out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ), + oSpace = o.match(/\s+/g), + nSpace = n.match(/\s+/g); - var oSpace = o.match(/\s+/g); - if (oSpace == null) { - oSpace = [" "]; + if ( oSpace == null ) { + oSpace = [ " " ]; } else { - oSpace.push(" "); + oSpace.push( " " ); } - var nSpace = n.match(/\s+/g); - if (nSpace == null) { - nSpace = [" "]; + + if ( nSpace == null ) { + nSpace = [ " " ]; } else { - nSpace.push(" "); + nSpace.push( " " ); } - if (out.n.length == 0) { - for (var i = 0; i < out.o.length; i++) { - str += '<del>' + out.o[i] + oSpace[i] + "</del>"; + if ( out.n.length === 0 ) { + for ( i = 0; i < out.o.length; i++ ) { + str += "<del>" + out.o[i] + oSpace[i] + "</del>"; } } else { - if (out.n[0].text == null) { - for (n = 0; n < out.o.length && out.o[n].text == null; n++) { - str += '<del>' + out.o[n] + oSpace[n] + "</del>"; + if ( out.n[0].text == null ) { + for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) { + str += "<del>" + out.o[n] + oSpace[n] + "</del>"; } } - for (var i = 0; i < out.n.length; i++) { + for ( i = 0; i < out.n.length; i++ ) { if (out.n[i].text == null) { - str += '<ins>' + out.n[i] + nSpace[i] + "</ins>"; + str += "<ins>" + out.n[i] + nSpace[i] + "</ins>"; } else { - var pre = ""; + // `pre` initialized at top of scope + pre = ""; - for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { - pre += '<del>' + out.o[n] + oSpace[n] + "</del>"; + for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) { + pre += "<del>" + out.o[n] + oSpace[n] + "</del>"; } str += " " + out.n[i].text + nSpace[i] + pre; } @@ -1626,7 +1921,12 @@ QUnit.diff = (function() { return str; }; -})(); +}()); + +// for CommonJS enviroments, export everything +if ( typeof exports !== "undefined" ) { + extend(exports, QUnit); +} // get at whatever the global object is, like window in browsers -})( (function() {return this}).call() ); +}( (function() {return this;}.call()) )); diff --git a/addons/web/static/lib/qweb/qweb2.js b/addons/web/static/lib/qweb/qweb2.js index c9b065ee69f..71a9628eff2 100644 --- a/addons/web/static/lib/qweb/qweb2.js +++ b/addons/web/static/lib/qweb/qweb2.js @@ -4,7 +4,7 @@ // TODO: t-set + t-value + children node == scoped variable ? var QWeb2 = { expressions_cache: {}, - RESERVED_WORDS: 'true,false,NaN,null,undefined,debugger,console,in,instanceof,new,function,return,this,typeof,eval,void,Math,RegExp,Array,Object,Date'.split(','), + RESERVED_WORDS: 'true,false,NaN,null,undefined,debugger,console,window,in,instanceof,new,function,return,this,typeof,eval,void,Math,RegExp,Array,Object,Date'.split(','), ACTIONS_PRECEDENCE: 'foreach,if,call,set,esc,escf,raw,rawf,js,debug,log'.split(','), WORD_REPLACEMENT: { 'and': '&&', diff --git a/addons/web/static/lib/spinjs/spin.js b/addons/web/static/lib/spinjs/spin.js new file mode 100644 index 00000000000..d8e21786a1f --- /dev/null +++ b/addons/web/static/lib/spinjs/spin.js @@ -0,0 +1,306 @@ +//fgnass.github.com/spin.js#v1.2.5 +(function(window, document, undefined) { + +/** + * Copyright (c) 2011 Felix Gnass [fgnass at neteye dot de] + * Licensed under the MIT license + */ + + var prefixes = ['webkit', 'Moz', 'ms', 'O']; /* Vendor prefixes */ + var animations = {}; /* Animation rules keyed by their name */ + var useCssAnimations; + + /** + * Utility function to create elements. If no tag name is given, + * a DIV is created. Optionally properties can be passed. + */ + function createEl(tag, prop) { + var el = document.createElement(tag || 'div'); + var n; + + for(n in prop) { + el[n] = prop[n]; + } + return el; + } + + /** + * Appends children and returns the parent. + */ + function ins(parent /* child1, child2, ...*/) { + for (var i=1, n=arguments.length; i<n; i++) { + parent.appendChild(arguments[i]); + } + return parent; + } + + /** + * Insert a new stylesheet to hold the @keyframe or VML rules. + */ + var sheet = function() { + var el = createEl('style'); + ins(document.getElementsByTagName('head')[0], el); + return el.sheet || el.styleSheet; + }(); + + /** + * Creates an opacity keyframe animation rule and returns its name. + * Since most mobile Webkits have timing issues with animation-delay, + * we create separate rules for each line/segment. + */ + function addAnimation(alpha, trail, i, lines) { + var name = ['opacity', trail, ~~(alpha*100), i, lines].join('-'); + var start = 0.01 + i/lines*100; + var z = Math.max(1-(1-alpha)/trail*(100-start) , alpha); + var prefix = useCssAnimations.substring(0, useCssAnimations.indexOf('Animation')).toLowerCase(); + var pre = prefix && '-'+prefix+'-' || ''; + + if (!animations[name]) { + sheet.insertRule( + '@' + pre + 'keyframes ' + name + '{' + + '0%{opacity:'+z+'}' + + start + '%{opacity:'+ alpha + '}' + + (start+0.01) + '%{opacity:1}' + + (start+trail)%100 + '%{opacity:'+ alpha + '}' + + '100%{opacity:'+ z + '}' + + '}', 0); + animations[name] = 1; + } + return name; + } + + /** + * Tries various vendor prefixes and returns the first supported property. + **/ + function vendor(el, prop) { + var s = el.style; + var pp; + var i; + + if(s[prop] !== undefined) return prop; + prop = prop.charAt(0).toUpperCase() + prop.slice(1); + for(i=0; i<prefixes.length; i++) { + pp = prefixes[i]+prop; + if(s[pp] !== undefined) return pp; + } + } + + /** + * Sets multiple style properties at once. + */ + function css(el, prop) { + for (var n in prop) { + el.style[vendor(el, n)||n] = prop[n]; + } + return el; + } + + /** + * Fills in default values. + */ + function merge(obj) { + for (var i=1; i < arguments.length; i++) { + var def = arguments[i]; + for (var n in def) { + if (obj[n] === undefined) obj[n] = def[n]; + } + } + return obj; + } + + /** + * Returns the absolute page-offset of the given element. + */ + function pos(el) { + var o = {x:el.offsetLeft, y:el.offsetTop}; + while((el = el.offsetParent)) { + o.x+=el.offsetLeft; + o.y+=el.offsetTop; + } + return o; + } + + var defaults = { + lines: 12, // The number of lines to draw + length: 7, // The length of each line + width: 5, // The line thickness + radius: 10, // The radius of the inner circle + rotate: 0, // rotation offset + color: '#000', // #rgb or #rrggbb + speed: 1, // Rounds per second + trail: 100, // Afterglow percentage + opacity: 1/4, // Opacity of the lines + fps: 20, // Frames per second when using setTimeout() + zIndex: 2e9, // Use a high z-index by default + className: 'spinner', // CSS class to assign to the element + top: 'auto', // center vertically + left: 'auto' // center horizontally + }; + + /** The constructor */ + var Spinner = function Spinner(o) { + if (!this.spin) return new Spinner(o); + this.opts = merge(o || {}, Spinner.defaults, defaults); + }; + + Spinner.defaults = {}; + merge(Spinner.prototype, { + spin: function(target) { + this.stop(); + var self = this; + var o = self.opts; + var el = self.el = css(createEl(0, {className: o.className}), {position: 'relative', zIndex: o.zIndex}); + var mid = o.radius+o.length+o.width; + var ep; // element position + var tp; // target position + + if (target) { + target.insertBefore(el, target.firstChild||null); + tp = pos(target); + ep = pos(el); + css(el, { + left: (o.left == 'auto' ? tp.x-ep.x + (target.offsetWidth >> 1) : o.left+mid) + 'px', + top: (o.top == 'auto' ? tp.y-ep.y + (target.offsetHeight >> 1) : o.top+mid) + 'px' + }); + } + + el.setAttribute('aria-role', 'progressbar'); + self.lines(el, self.opts); + + if (!useCssAnimations) { + // No CSS animation support, use setTimeout() instead + var i = 0; + var fps = o.fps; + var f = fps/o.speed; + var ostep = (1-o.opacity)/(f*o.trail / 100); + var astep = f/o.lines; + + !function anim() { + i++; + for (var s=o.lines; s; s--) { + var alpha = Math.max(1-(i+s*astep)%f * ostep, o.opacity); + self.opacity(el, o.lines-s, alpha, o); + } + self.timeout = self.el && setTimeout(anim, ~~(1000/fps)); + }(); + } + return self; + }, + stop: function() { + var el = this.el; + if (el) { + clearTimeout(this.timeout); + if (el.parentNode) el.parentNode.removeChild(el); + this.el = undefined; + } + return this; + }, + lines: function(el, o) { + var i = 0; + var seg; + + function fill(color, shadow) { + return css(createEl(), { + position: 'absolute', + width: (o.length+o.width) + 'px', + height: o.width + 'px', + background: color, + boxShadow: shadow, + transformOrigin: 'left', + transform: 'rotate(' + ~~(360/o.lines*i+o.rotate) + 'deg) translate(' + o.radius+'px' +',0)', + borderRadius: (o.width>>1) + 'px' + }); + } + for (; i < o.lines; i++) { + seg = css(createEl(), { + position: 'absolute', + top: 1+~(o.width/2) + 'px', + transform: o.hwaccel ? 'translate3d(0,0,0)' : '', + opacity: o.opacity, + animation: useCssAnimations && addAnimation(o.opacity, o.trail, i, o.lines) + ' ' + 1/o.speed + 's linear infinite' + }); + if (o.shadow) ins(seg, css(fill('#000', '0 0 4px ' + '#000'), {top: 2+'px'})); + ins(el, ins(seg, fill(o.color, '0 0 1px rgba(0,0,0,.1)'))); + } + return el; + }, + opacity: function(el, i, val) { + if (i < el.childNodes.length) el.childNodes[i].style.opacity = val; + } + }); + + ///////////////////////////////////////////////////////////////////////// + // VML rendering for IE + ///////////////////////////////////////////////////////////////////////// + + /** + * Check and init VML support + */ + !function() { + + function vml(tag, attr) { + return createEl('<' + tag + ' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">', attr); + } + + var s = css(createEl('group'), {behavior: 'url(#default#VML)'}); + + if (!vendor(s, 'transform') && s.adj) { + + // VML support detected. Insert CSS rule ... + sheet.addRule('.spin-vml', 'behavior:url(#default#VML)'); + + Spinner.prototype.lines = function(el, o) { + var r = o.length+o.width; + var s = 2*r; + + function grp() { + return css(vml('group', {coordsize: s +' '+s, coordorigin: -r +' '+-r}), {width: s, height: s}); + } + + var margin = -(o.width+o.length)*2+'px'; + var g = css(grp(), {position: 'absolute', top: margin, left: margin}); + + var i; + + function seg(i, dx, filter) { + ins(g, + ins(css(grp(), {rotation: 360 / o.lines * i + 'deg', left: ~~dx}), + ins(css(vml('roundrect', {arcsize: 1}), { + width: r, + height: o.width, + left: o.radius, + top: -o.width>>1, + filter: filter + }), + vml('fill', {color: o.color, opacity: o.opacity}), + vml('stroke', {opacity: 0}) // transparent stroke to fix color bleeding upon opacity change + ) + ) + ); + } + + if (o.shadow) { + for (i = 1; i <= o.lines; i++) { + seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)'); + } + } + for (i = 1; i <= o.lines; i++) seg(i); + return ins(el, g); + }; + Spinner.prototype.opacity = function(el, i, val, o) { + var c = el.firstChild; + o = o.shadow && o.lines || 0; + if (c && i+o < c.childNodes.length) { + c = c.childNodes[i+o]; c = c && c.firstChild; c = c && c.firstChild; + if (c) c.opacity = val; + } + }; + } + else { + useCssAnimations = vendor(s, 'animation'); + } + }(); + + window.Spinner = Spinner; + +})(window, document);
\ No newline at end of file diff --git a/addons/web/static/src/css/Makefile b/addons/web/static/src/css/Makefile index eca044497ef..d6b4f4b2fc8 100644 --- a/addons/web/static/src/css/Makefile +++ b/addons/web/static/src/css/Makefile @@ -1,3 +1,3 @@ base.css: base.sass - sass -t expanded base.sass base.css + sass --trace -t expanded base.sass base.css diff --git a/addons/web/static/src/css/base.css b/addons/web/static/src/css/base.css index 5ef56c69797..18db06c44a1 100644 --- a/addons/web/static/src/css/base.css +++ b/addons/web/static/src/css/base.css @@ -20,11 +20,21 @@ font-style: normal; } -.openerp.openerp-web-client-container { +@media print { + .oe_topbar, .oe_leftbar, .oe_loading { + display: none !important; + } +} +.openerp.openerp_webclient_container { height: 100%; position: relative; } +.text-tag .text-button { + height: auto !important; + min-height: 16px; +} + .openerp { padding: 0; margin: 0; @@ -32,16 +42,25 @@ color: #4c4c4c; font-size: 13px; background: white; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5); /* http://www.quirksmode.org/dom/inputfile.html * http://stackoverflow.com/questions/2855589/replace-input-type-file-by-an-image */ } +.openerp :-moz-placeholder { + color: #afafb6 !important; + font-style: italic !important; +} +.openerp ::-webkit-input-placeholder { + color: #afafb6 !important; + font-style: italic !important; +} .openerp a { text-decoration: none; + cursor: pointer !important; } .openerp table { padding: 0; - font-size: 13px; border-collapse: collapse; } .openerp thead { @@ -84,63 +103,24 @@ background-image: -o-linear-gradient(top, #eeeeee, #dedede); background-image: linear-gradient(to bottom, #eeeeee, #dedede); } -.openerp .numeric { - text-align: right; - width: 82px; -} -.openerp .numeric input { - text-align: right; -} -.openerp ul, .openerp li, .openerp ol { - margin: 0; - padding: 0; -} -.openerp li { - list-style-type: none; -} -.openerp input, .openerp textarea { +.openerp input, .openerp textarea, .openerp select { + padding: 2px 4px; border: 1px solid #cccccc; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; + background: white; } -.openerp .oe_i { - font-family: "mnmliconsRegular" !important; - font-size: 21px; - font-weight: 300 !important; -} -.openerp .oe_e { - font-family: "entypoRegular" !important; - font-size: 34px; - font-weight: 300 !important; -} -.openerp .oe_left { - float: left; - margin-right: 8px; -} -.openerp .oe_right { - float: right; - margin-left: 8px; -} -.openerp .oe_text_right { - text-align: right; -} -.openerp .oe_clear { - clear: both; -} -.openerp .oe_wait { - cursor: wait; -} -.openerp .oe_fade { - color: #888888; +.openerp img { + vertical-align: middle; } -.openerp .oe_bold { - font-weight: bold; +.openerp h4 { + margin: 4px 0; } -.openerp a.button:link, .openerp a.button:visited, .openerp button, .openerp input[type='submit'] { +.openerp a.button:link, .openerp a.button:visited, .openerp button, .openerp input[type='submit'], .openerp .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button { display: inline-block; - border: 1px solid #ababab; - color: #404040; + border: 1px solid rgba(0, 0, 0, 0.4); + color: #4c4c4c; margin: 0; padding: 3px 12px; font-size: 13px; @@ -157,12 +137,12 @@ border-radius: 3px; -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset; -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset; - -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset; text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5); -webkit-font-smoothing: antialiased; outline: none; } -.openerp a.button:hover, .openerp button:hover, .openerp input[type='submit']:hover { +.openerp a.button:hover, .openerp button:hover, .openerp input[type='submit']:hover, .openerp .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-hover { background-color: #f6f6f6; background-image: -webkit-gradient(linear, left top, left bottom, from(#f6f6f6), to(#e3e3e3)); background-image: -webkit-linear-gradient(top, #f6f6f6, #e3e3e3); @@ -171,9 +151,11 @@ background-image: -o-linear-gradient(top, #f6f6f6, #e3e3e3); background-image: linear-gradient(to bottom, #f6f6f6, #e3e3e3); cursor: pointer; + background-position: 0; } -.openerp a.button:focus, .openerp button:focus, .openerp input[type='submit']:focus { +.openerp a.button:focus, .openerp button:focus, .openerp input[type='submit']:focus, .openerp .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-focus { border: 1px solid #80bfff; + background-position: 0; background-color: #f6f6f6; background-image: -webkit-gradient(linear, left top, left bottom, from(#f6f6f6), to(#e3e3e3)); background-image: -webkit-linear-gradient(top, #f6f6f6, #e3e3e3); @@ -183,29 +165,256 @@ background-image: linear-gradient(to bottom, #f6f6f6, #e3e3e3); -moz-box-shadow: 0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, 0.8) inset; -webkit-box-shadow: 0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, 0.8) inset; - -box-shadow: 0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, 0.8) inset; -} -.openerp a.button:active, .openerp a.button.active, .openerp button:active, .openerp button.active, .openerp input[type='submit']:active, .openerp input[type='submit'].active { - background: #e3e3e3; - background: -moz-linear-gradient(top, #e3e3e3, #f6f6f6) #1b468f; - background: -webkit-gradient(linear, left top, left bottom, from(#e3e3e3), to(#f6f6f6)) #1b468f; - background: linear-gradient(top, #e3e3e3, #f6f6f6) #1b468f; + box-shadow: 0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, 0.8) inset; +} +.openerp a.button:active, .openerp a.button.active, .openerp button:active, .openerp button.active, .openerp input[type='submit']:active, .openerp input[type='submit'].active, .openerp .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-active { + background-color: #e3e3e3; + background-image: -webkit-gradient(linear, left top, left bottom, from(#e3e3e3), to(#f6f6f6)); + background-image: -webkit-linear-gradient(top, #e3e3e3, #f6f6f6); + background-image: -moz-linear-gradient(top, #e3e3e3, #f6f6f6); + background-image: -ms-linear-gradient(top, #e3e3e3, #f6f6f6); + background-image: -o-linear-gradient(top, #e3e3e3, #f6f6f6); + background-image: linear-gradient(to bottom, #e3e3e3, #f6f6f6); -moz-box-shadow: none; -webkit-box-shadow: none; - -box-shadow: none; + box-shadow: none; } .openerp a.button.disabled, .openerp button:disabled, .openerp input[type='submit']:disabled { background: #efefef !important; border: 1px solid #d1d1d1 !important; -moz-box-shadow: none !important; -webkit-box-shadow: none !important; - -box-shadow: none !important; + box-shadow: none !important; color: #aaaaaa !important; cursor: default; text-shadow: 0 1px 1px white !important; } -.openerp button.primary { +.openerp .ui-widget-content a { + color: #7c7bad; +} +.openerp .oe_bounce_container { + display: inline-block; +} +.openerp.ui-dialog { + display: none; + padding: 6px; + background-color: rgba(60, 60, 60, 0.7); + border: 1px solid; + border-color: #888888 #555555 #444444; + -moz-border-radius: 8px; + -webkit-border-radius: 8px; + border-radius: 8px; + -moz-box-shadow: 0 1px 12px rgba(0, 0, 0, 0.6); + -webkit-box-shadow: 0 1px 12px rgba(0, 0, 0, 0.6); + box-shadow: 0 1px 12px rgba(0, 0, 0, 0.6); + -webkit-background-clip: padding-box; + -moz-background-clip: padding-box; + background-clip: padding-box; +} +.openerp.ui-dialog .ui-dialog-content { + padding: 0px; +} +.openerp.ui-dialog .ui-dialog-titlebar, .openerp.ui-dialog .ui-dialog-content, .openerp.ui-dialog .ui-dialog-buttonpane { + padding: 16px; +} +.openerp.ui-dialog .ui-dialog-titlebar { + border-bottom: 1px solid #cacaca; + -moz-border-radius: 2px 2px 0 0; + -webkit-border-radius: 2px 2px 0 0; + border-radius: 2px 2px 0 0; + background-color: #fcfcfc; + background-image: -webkit-gradient(linear, left top, left bottom, from(#fcfcfc), to(#dedede)); + background-image: -webkit-linear-gradient(top, #fcfcfc, #dedede); + background-image: -moz-linear-gradient(top, #fcfcfc, #dedede); + background-image: -ms-linear-gradient(top, #fcfcfc, #dedede); + background-image: -o-linear-gradient(top, #fcfcfc, #dedede); + background-image: linear-gradient(to bottom, #fcfcfc, #dedede); +} +.openerp.ui-dialog .ui-dialog-titlebar .ui-dialog-title { + margin: 0; + padding: 0; +} +.openerp.ui-dialog .ui-widget-header { + border: none; +} +.openerp.ui-dialog .ui-dialog-content { + background: white; +} +.openerp.ui-dialog .ui-dialog-buttonpane { + border-top: 1px solid #e0e0e0; + background: #f5f7f9; + margin: 0; + -moz-border-radius: 0 0 2px 2px; + -webkit-border-radius: 0 0 2px 2px; + border-radius: 0 0 2px 2px; +} +.openerp.ui-dialog .ui-dialog-buttonpane button { + margin: 0 4px 0 0; +} +.openerp.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { + float: left; +} +.openerp.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button { + margin-right: 4px; +} +.openerp.ui-dialog .ui-dialog-titlebar-close { + padding: 0; +} +.openerp.ui-dialog .ui-dialog-titlebar-close .ui-icon-closethick { + display: none; +} +.openerp.ui-dialog .ui-dialog-titlebar-close:before { + content: "×"; + font-size: 18px; + font-weight: bold; + line-height: 16px; + color: black; + text-shadow: 0 1px 0 white; + padding: 0; + cursor: pointer; + background: transparent; + border: 0; +} +.openerp.ui-dialog .ui-dialog-titlebar-close:before:hover { + color: black; + text-decoration: none; +} +.openerp.ui-dialog .oe_about { + background-color: white; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAKUlEQVQIHWO8e/fufwYsgAUkJigoiCIF5DMyoYggcUiXgNnBiGQKmAkARpcEQeriln4AAAAASUVORK5CYII=); + -moz-border-radius: 0 0 2px 2px; + -webkit-border-radius: 0 0 2px 2px; + border-radius: 0 0 2px 2px; +} +.openerp.ui-dialog .oe_about a { + color: #7c7bad; +} +.openerp.ui-dialog .oe_about a:hover { + text-decoration: underline; +} +.openerp.ui-dialog .oe_about a:focus { + outline: none; +} +.openerp.ui-dialog .oe_about .oe_logo { + margin-left: -6px; +} +.openerp.ui-dialog .oe_about .oe_bottom { + position: absolute; + top: 50%; + left: 0; + right: 0; + bottom: 0; + text-shadow: 0 1px 1px #999999; + background-color: #b41616; + background-image: -webkit-gradient(linear, left top, left bottom, from(#b41616), to(#600606)); + background-image: -webkit-linear-gradient(top, #b41616, #600606); + background-image: -moz-linear-gradient(top, #b41616, #600606); + background-image: -ms-linear-gradient(top, #b41616, #600606); + background-image: -o-linear-gradient(top, #b41616, #600606); + background-image: linear-gradient(to bottom, #b41616, #600606); + color: #eeeeee; + padding: 0 16px; + -moz-border-radius: 0 0 2px 2px; + -webkit-border-radius: 0 0 2px 2px; + border-radius: 0 0 2px 2px; +} +.openerp.ui-dialog .oe_about .oe_bottom a { + color: #eeeeee; +} +.openerp.ui-dialog.oe_act_window .ui-dialog-content { + padding: 0px; +} +.openerp .modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: black; + filter: alpha(opacity=30); + opacity: 0.3; +} +.openerp .oe_i { + font-family: "mnmliconsRegular" !important; + font-size: 21px; + font-weight: 300 !important; +} +.openerp .oe_e { + font-family: "entypoRegular" !important; + font-size: 34px; + font-weight: 300 !important; +} +.openerp .oe_left { + float: left; + margin-right: 8px; +} +.openerp .oe_right { + float: right; + margin-left: 8px; +} +.openerp .oe_text_right { + text-align: right; +} +.openerp .oe_clear { + clear: both; +} +.openerp .oe_wait { + cursor: wait; +} +.openerp .oe_fade { + color: #888888; + font-weight: normal; +} +.openerp .oe_bold { + font-weight: bold; +} +.openerp .oe_inline { + width: auto !important; +} +.openerp .oe_highlight { color: white; + background: #dc5f59; +} +.openerp button.oe_highlight { + background-color: #df3f3f; + background-image: -webkit-gradient(linear, left top, left bottom, from(#df3f3f), to(#a21a1a)); + background-image: -webkit-linear-gradient(top, #df3f3f, #a21a1a); + background-image: -moz-linear-gradient(top, #df3f3f, #a21a1a); + background-image: -ms-linear-gradient(top, #df3f3f, #a21a1a); + background-image: -o-linear-gradient(top, #df3f3f, #a21a1a); + background-image: linear-gradient(to bottom, #df3f3f, #a21a1a); + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(155, 155, 155, 0.4) inset; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(155, 155, 155, 0.4) inset; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(155, 155, 155, 0.4) inset; +} +.openerp button.oe_highlight:hover { + background-color: #e25858; + background-image: -webkit-gradient(linear, left top, left bottom, from(#e25858), to(#ab1b1b)); + background-image: -webkit-linear-gradient(top, #e25858, #ab1b1b); + background-image: -moz-linear-gradient(top, #e25858, #ab1b1b); + background-image: -ms-linear-gradient(top, #e25858, #ab1b1b); + background-image: -o-linear-gradient(top, #e25858, #ab1b1b); + background-image: linear-gradient(to bottom, #e25858, #ab1b1b); +} +.openerp button.oe_highlight:active { + background-color: #c52020; + background-image: -webkit-gradient(linear, left top, left bottom, from(#c52020), to(#d22323)); + background-image: -webkit-linear-gradient(top, #c52020, #d22323); + background-image: -moz-linear-gradient(top, #c52020, #d22323); + background-image: -ms-linear-gradient(top, #c52020, #d22323); + background-image: -o-linear-gradient(top, #c52020, #d22323); + background-image: linear-gradient(to bottom, #c52020, #d22323); +} +.openerp .oe_background_grey { + background: #eeeeee !important; +} +.openerp .oe_form_dirty .oe_highlight_on_dirty { + color: white; + background: #dc5f59; + font-weight: bold; +} +.openerp .oe_form_dirty button.oe_highlight_on_dirty { background-color: #dc5f59; background-image: -webkit-gradient(linear, left top, left bottom, from(#dc5f59), to(#b33630)); background-image: -webkit-linear-gradient(top, #dc5f59, #b33630); @@ -215,54 +424,352 @@ background-image: linear-gradient(to bottom, #dc5f59, #b33630); -moz-box-shadow: none; -webkit-box-shadow: none; - -box-shadow: none; -} -.openerp button.primary:hover { - background-color: #df6b66; - background-image: -webkit-gradient(linear, left top, left bottom, from(#df6b66), to(#bf3a33)); - background-image: -webkit-linear-gradient(top, #df6b66, #bf3a33); - background-image: -moz-linear-gradient(top, #df6b66, #bf3a33); - background-image: -ms-linear-gradient(top, #df6b66, #bf3a33); - background-image: -o-linear-gradient(top, #df6b66, #bf3a33); - background-image: linear-gradient(to bottom, #df6b66, #bf3a33); - -moz-box-shadow: 0 0 1px rgba(0, 0, 0, 0.2); - -webkit-box-shadow: 0 0 1px rgba(0, 0, 0, 0.2); - -box-shadow: 0 0 1px rgba(0, 0, 0, 0.2); -} -.openerp button.primary:active { - background-color: #b33630; - background-image: -webkit-gradient(linear, left top, left bottom, from(#b33630), to(#dc5f59)); - background-image: -webkit-linear-gradient(top, #b33630, #dc5f59); - background-image: -moz-linear-gradient(top, #b33630, #dc5f59); - background-image: -ms-linear-gradient(top, #b33630, #dc5f59); - background-image: -o-linear-gradient(top, #b33630, #dc5f59); - background-image: linear-gradient(to bottom, #b33630, #dc5f59); + box-shadow: none; +} +.openerp .oe_form_dirty button.oe_highlight_on_dirty:hover { + background: #ed6f6a; +} +.openerp .oe_title { + width: 50%; + float: left; +} +.openerp .oe_title:after { + content: "."; + display: block; + height: 0; + clear: both; + visibility: hidden; +} +.openerp .oe_button_box { + width: 270px; + text-align: right; +} +.openerp .oe_button_box button { + margin: 4px; +} +.openerp .oe_avatar > img { + max-height: 90px; + max-width: 90px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); + -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4); + border: none; + margin-bottom: 10px; +} +.openerp .oe_avatar + div { + margin-left: 5px; +} +.openerp .oe_image_small > img { + max-width: 50px; + max-height: 50px; +} +.openerp .oe_image_medium > img { + max-width: 180px; + max-height: 180px; +} +.openerp .oe_button.oe_link { + border: none; + padding: 0; + margin: 0; + background: none; + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; -moz-box-shadow: none; -webkit-box-shadow: none; - -box-shadow: none; + box-shadow: none; +} +.openerp .oe_button.oe_link img { + display: none; +} +.openerp .oe_button.oe_link span { + border: none; + padding: 0; + margin: 0; + background: none; + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; + color: #7c7bad; + font-weight: bold; +} +.openerp .oe_button.oe_link span:hover { + text-decoration: underline; +} +.openerp .oe_webclient .oe_star_on, .openerp .oe_webclient .oe_star_off { + color: #cccccc; + text-shadow: 0 0 2px black; + vertical-align: top; + position: relative; + top: -5px; +} +.openerp .oe_webclient .oe_star_on:hover, .openerp .oe_webclient .oe_star_off:hover { + text-decoration: none; +} +.openerp .oe_webclient .oe_star_on { + color: gold; +} +.openerp .oe_tag { + border: 1px solid #afafb6; + font-size: 11px; + padding: 2px 4px; + margin: 0 2px 2px 0; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + background: #f0f0fa; + color: #4c4c4c; +} +.openerp .oe_tag_dark { + background: #7c7bad; + color: #eeeeee; +} +.openerp .oe_tags { + margin-bottom: 1px; +} +.openerp .oe_tags.oe_inline { + min-width: 250px; +} +.openerp .oe_tags .text-wrap { + width: 100% !important; +} +.openerp .oe_tags .text-wrap textarea { + width: 100% !important; +} +.openerp .oe_tags .text-core .text-wrap .text-dropdown .text-list .text-suggestion em { + font-style: italic; + text-decoration: none; +} +.openerp.oe_tooltip { + font-size: 12px; +} +.openerp.oe_tooltip .oe_tooltip_string { + color: #ffdd55; + font-weight: bold; + font-size: 13px; +} +.openerp.oe_tooltip .oe_tooltip_help { + white-space: pre-wrap; +} +.openerp.oe_tooltip .oe_tooltip_technical { + padding: 0 0 4px 0; + margin: 5px 0 0 15px; +} +.openerp.oe_tooltip .oe_tooltip_technical li { + list-style: circle; +} +.openerp.oe_tooltip .oe_tooltip_technical_title { + font-weight: bold; } -.openerp button.following { +.openerp.oe_tooltip .oe_tooltip_close { + margin: -5px 0 0 2px; + cursor: default; + float: right; color: white; - width: 120px; - background-color: #8a89ba; - background-image: -webkit-gradient(linear, left top, left bottom, from(#8a89ba), to(#807fb4)); - background-image: -webkit-linear-gradient(top, #8a89ba, #807fb4); - background-image: -moz-linear-gradient(top, #8a89ba, #807fb4); - background-image: -ms-linear-gradient(top, #8a89ba, #807fb4); - background-image: -o-linear-gradient(top, #8a89ba, #807fb4); - background-image: linear-gradient(to bottom, #8a89ba, #807fb4); -} -.openerp button.unfollow { +} +.openerp.oe_tooltip .oe_tooltip_close:hover { + color: #999999; + cursor: pointer; +} +.openerp.oe_tooltip .oe_tooltip_message { + max-width: 310px; +} +.openerp .oe_notebook { + margin: 8px 0; + padding: 0 16px; + list-style: none; + zoom: 1; +} +.openerp .oe_notebook.ui-corner-all { + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; +} +.openerp .oe_notebook:before, .openerp .oe_notebook:after { + display: table; + content: ""; + zoom: 1; +} +.openerp .oe_notebook:after { + clear: both; +} +.openerp .oe_notebook > li { + float: left; +} +.openerp .oe_notebook > li > a { + display: block; + color: #4c4c4c; +} +.openerp .oe_notebook { + border-color: #dddddd; + border-style: solid; + border-width: 0 0 1px; +} +.openerp .oe_notebook > li { + position: relative; +} +.openerp .oe_notebook > li > a { + padding: 0 12px; + margin-right: 2px; + line-height: 30px; + border: 1px solid transparent; + -moz-border-radius: 4px 4px 0 0; + -webkit-border-radius: 4px 4px 0 0; + border-radius: 4px 4px 0 0; +} +.openerp .oe_notebook > li > a:hover { + text-decoration: none; + background-color: #eeeeee; + border-color: #eeeeee #eeeeee #dddddd; +} +.openerp .oe_notebook > li.ui-state-active > a, .openerp .oe_notebook > li.ui-state-active > a:hover { + background-color: white; + border: 1px solid #dddddd; + border-bottom-color: transparent; + cursor: default; +} +.openerp .oe_notebook_page { + padding: 0; +} +.openerp div.ui-tabs { + padding: 3px 0px 3px 0px; +} +.openerp .ui-tabs-hide { + display: none; +} +.openerp .oe_dropdown, .openerp .oe_dropdown_hover, .openerp .oe_dropdown_toggle { + position: relative; + cursor: pointer; +} +.openerp .oe_dropdown_toggle { + color: rgba(0, 0, 0, 0.5); + font-weight: normal; +} +.openerp .oe_dropdown_hover:hover .oe_dropdown_menu, .openerp .oe_dropdown_menu.oe_opened { + display: block; +} +.openerp .oe_dropdown_menu { + display: none; + position: absolute; + top: 26px; + left: 0; + z-index: 3; + margin: 0; + padding: 0; + border: 1px solid #afafb6; + background: white; + padding: 4px 0; + min-width: 140px; + text-align: left; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); +} +.openerp .oe_dropdown_menu > li { + list-style-type: none; + float: none; + display: block; + position: relative; + margin: 0; + padding: 2px 8px; +} +.openerp .oe_dropdown_menu > li:hover { + background-color: #f0f0fa; + background-image: -webkit-gradient(linear, left top, left bottom, from(#f0f0fa), to(#eeeef6)); + background-image: -webkit-linear-gradient(top, #f0f0fa, #eeeef6); + background-image: -moz-linear-gradient(top, #f0f0fa, #eeeef6); + background-image: -ms-linear-gradient(top, #f0f0fa, #eeeef6); + background-image: -o-linear-gradient(top, #f0f0fa, #eeeef6); + background-image: linear-gradient(to bottom, #f0f0fa, #eeeef6); + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} +.openerp .oe_dropdown_menu > li > a { + white-space: nowrap; + display: block; + color: #4c4c4c; + text-decoration: none; +} +.openerp .oe_dropdown_menu > li > a:hover { + text-decoration: none; +} +.openerp .oe_dropdown_arrow:after { + width: 0; + height: 0; + display: inline-block; + content: "&darr"; + text-indent: -99999px; + vertical-align: top; + margin-top: 8px; + margin-left: 3px; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px solid #404040; + filter: alpha(opacity=50); + opacity: 0.5; +} +.openerp .oe_sidebar { + white-space: nowrap; +} +.openerp .oe_sidebar .oe_dropdown_menu .oe_sidebar_add_attachment { + height: 20px; + cursor: pointer; + padding-left: 6px; + margin-top: 6px; +} +.openerp .oe_sidebar .oe_dropdown_menu .oe_sidebar_add_attachment span { + font-weight: bold; +} +.openerp .oe_sidebar .oe_dropdown_menu .oe_sidebar_add_attachment .oe_hidden_input_file { + width: 200px; +} +.openerp .oe_sidebar .oe_dropdown_menu .oe_sidebar_add_attachment:hover { + background-color: #f0f0fa; + background-image: -webkit-gradient(linear, left top, left bottom, from(#f0f0fa), to(#eeeef6)); + background-image: -webkit-linear-gradient(top, #f0f0fa, #eeeef6); + background-image: -moz-linear-gradient(top, #f0f0fa, #eeeef6); + background-image: -ms-linear-gradient(top, #f0f0fa, #eeeef6); + background-image: -o-linear-gradient(top, #f0f0fa, #eeeef6); + background-image: linear-gradient(to bottom, #f0f0fa, #eeeef6); + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} +.openerp .oe_sidebar .oe_dropdown_menu li .oe_sidebar_delete_item { + position: absolute; + top: 4px; + right: 4px; display: none; + width: 12px; + height: 12px; + padding: 1px; + color: #8786b7; + line-height: 8px; + text-align: center; + font-weight: bold; + text-shadow: 0 1px 1px white; +} +.openerp .oe_sidebar .oe_dropdown_menu li .oe_sidebar_delete_item:hover { + text-decoration: none; color: white; - width: 120px; - background-color: #dc5f59; - background-image: -webkit-gradient(linear, left top, left bottom, from(#dc5f59), to(#b33630)); - background-image: -webkit-linear-gradient(top, #dc5f59, #b33630); - background-image: -moz-linear-gradient(top, #dc5f59, #b33630); - background-image: -ms-linear-gradient(top, #dc5f59, #b33630); - background-image: -o-linear-gradient(top, #dc5f59, #b33630); - background-image: linear-gradient(to bottom, #dc5f59, #b33630); + background: #8786b7; + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4); + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; +} +.openerp .oe_sidebar .oe_dropdown_menu li:hover .oe_sidebar_delete_item { + display: inline-block; } .openerp .oe_loading { display: none; @@ -285,14 +792,15 @@ z-index: 1050; } .openerp .oe_login { - background: url("/web/static/src/img/pattern.png") repeat; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAKUlEQVQIHWO8e/fufwYsgAUkJigoiCIF5DMyoYggcUiXgNnBiGQKmAkARpcEQeriln4AAAAASUVORK5CYII=); text-align: center; font-size: 14px; height: 100%; } -.openerp .oe_login li { +.openerp .oe_login ul, .openerp .oe_login li { + margin: 0; list-style-type: none; - padding-bottom: 4px; + padding: 0 0 4px 0; } .openerp .oe_login button { float: right; @@ -314,16 +822,16 @@ border-radius: 4px; -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(155, 155, 155, 0.4) inset; -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(155, 155, 155, 0.4) inset; - -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(155, 155, 155, 0.4) inset; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(155, 155, 155, 0.4) inset; } .openerp .oe_login input, .openerp .oe_login select { width: 252px; font-family: "Lucida Grande", Helvetica, Verdana, Arial; border: 1px solid #999999; background: whitesmoke; - -moz-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.3); - -webkit-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.3); - -box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.3); + -moz-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.4); + -webkit-box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.4); + box-shadow: inset 0 1px 4px rgba(0, 0, 0, 0.4); -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; @@ -383,7 +891,7 @@ border-radius: 8px; -moz-box-shadow: 0 0 18px rgba(0, 0, 0, 0.9); -webkit-box-shadow: 0 0 18px rgba(0, 0, 0, 0.9); - -box-shadow: 0 0 18px rgba(0, 0, 0, 0.9); + box-shadow: 0 0 18px rgba(0, 0, 0, 0.9); } .openerp .oe_login .oe_login_pane h2 { margin-top: 0; @@ -427,7 +935,7 @@ border-radius: 4px; -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.8); -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.8); - -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.8); + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.8); } .openerp .oe_login_invalid .oe_login_error_message { display: inline-block; @@ -454,8 +962,8 @@ } .openerp .oe_topbar { width: 100%; - height: 31px; - border-top: solid 1px #d3d3d3; + height: 32px; + background-color: #414141; background-color: #646060; background-image: -webkit-gradient(linear, left top, left bottom, from(#646060), to(#262626)); background-image: -webkit-linear-gradient(top, #646060, #262626); @@ -464,191 +972,47 @@ background-image: -o-linear-gradient(top, #646060, #262626); background-image: linear-gradient(to bottom, #646060, #262626); } -.openerp .oe_topbar .oe_topbar_item li { - float: left; -} -.openerp .oe_topbar .oe_topbar_item li a { +.openerp .oe_topbar .oe_topbar_item { display: block; padding: 5px 10px 7px; line-height: 20px; height: 20px; + text-decoration: none; color: #eeeeee; vertical-align: top; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); -} -.openerp .oe_topbar .oe_topbar_item li a:hover { - background: #303030; - color: white; - -moz-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; - -webkit-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; - -box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; -} -.openerp .oe_topbar .oe_topbar_item .oe_active { - background: #303030; - font-weight: bold; + -webkit-transition: all 0.2s ease-out; + -moz-transition: all 0.2s ease-out; + -ms-transition: all 0.2s ease-out; + -o-transition: all 0.2s ease-out; + transition: all 0.2s ease-out; +} +.openerp .oe_topbar .oe_topbar_item:hover { + background: rgba(0, 0, 0, 0.2); + text-shadow: black 0px 0px 3px; color: white; - -moz-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; - -webkit-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; - -box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset; } .openerp .oe_topbar .oe_topbar_avatar { width: 24px; height: 24px; margin: -2px 2px 0 0; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - border-radius: 4px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; } .openerp .oe_topbar .oe_topbar_avatar { vertical-align: top; } -.openerp .oe_topbar .oe_dropdown_toggle:after { - width: 0; - height: 0; - display: inline-block; - content: "&darr"; - text-indent: -99999px; - vertical-align: top; - margin-top: 8px; - margin-left: 4px; - border-left: 4px solid transparent; - border-right: 4px solid transparent; +.openerp .oe_topbar .oe_dropdown_arrow:after { border-top: 4px solid white; - filter: alpha(opacity=50); - opacity: 0.5; -} -.openerp .oe_leftbar { - display: none; - width: 220px; - background: #f0eeee; - border-right: 1px solid #afafb6; - text-shadow: 0 1px 1px white; - padding-bottom: 16px; - line-height: 18px; -} -.openerp a.oe_logo { - width: 220px; - display: block; - text-align: center; - height: 70px; - line-height: 70px; -} -.openerp a.oe_logo img { - height: 40px; - width: 157px; - margin: 14px 0; -} -.openerp .oe_footer { - position: fixed; - bottom: 0; - padding: 4px 0; - background: #f0eeee; - width: 220px; - text-align: center; -} -.openerp .oe_footer a { - font-weight: 800; - font-family: serif; - font-size: 16px; - color: black; -} -.openerp .oe_footer a span { - color: #c81010; - font-style: italic; -} -.openerp .oe_user_menu { - float: right; - padding: 0; - margin: 0; -} -.openerp .oe_user_menu li { - list-style-type: none; - float: left; -} -.openerp .oe_user_menu .oe_dropdown { - position: relative; } -.openerp .oe_user_menu .oe_dropdown_options { - float: left; - background: #333333; - background: rgba(37, 37, 37, 0.9); - display: none; - position: absolute; +.openerp .oe_topbar .oe_dropdown_menu { top: 32px; - right: -1px; - border: 0; - z-index: 900; - margin-left: 0; - margin-right: 0; - padding: 6px 0; - zoom: 1; - border-color: #999999; - border-color: rgba(0, 0, 0, 0.2); - border-style: solid; - border-width: 0 1px 1px; - -moz-border-radius: 0 0 6px 6px; - -webkit-border-radius: 0 0 6px 6px; - border-radius: 0 0 6px 6px; - -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); - -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); - -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); - -webkit-background-clip: padding-box; - -moz-background-clip: padding-box; - background-clip: padding-box; -} -.openerp .oe_user_menu .oe_dropdown_options li { - float: none; - display: block; - background-color: none; -} -.openerp .oe_user_menu .oe_dropdown_options li a { - white-space: nowrap; - display: block; - padding: 4px 15px; - clear: both; - font-weight: normal; - line-height: 18px; - color: #eeeeee; -} -.openerp .oe_user_menu .oe_dropdown_options li a:hover { - background-color: #292929; - background-image: -webkit-gradient(linear, left top, left bottom, from(#292929), to(#191919)); - background-image: -webkit-linear-gradient(top, #292929, #191919); - background-image: -moz-linear-gradient(top, #292929, #191919); - background-image: -ms-linear-gradient(top, #292929, #191919); - background-image: -o-linear-gradient(top, #292929, #191919); - background-image: linear-gradient(to bottom, #292929, #191919); - -moz-box-shadow: none; - -webkit-box-shadow: none; - -box-shadow: none; -} -.openerp .oe_user_menu .oe_dropdown_options li hr { - border-top: 1px solid #999999; - border-bottom: 0; -} -.openerp .oe_systray > div { - float: left; - padding: 0 4px 0 4px; -} -.openerp .oe_systray { - float: right; -} -.openerp .oe_systray .oe_systray_dropdown { - position: relative; -} -.openerp .oe_systray .oe_systray_dropdown:hover .oe_systray_dropdown_options { - display: block; -} -.openerp .oe_systray .oe_systray_dropdown_options { background: #333333; background: rgba(37, 37, 37, 0.9); - display: none; - position: absolute; - z-index: 900; - border: 0; - margin-left: 0; - margin-right: 0; - padding: 6px 0; border-color: #999999; border-color: rgba(0, 0, 0, 0.2); border-style: solid; @@ -656,27 +1020,18 @@ -moz-border-radius: 0 0 6px 6px; -webkit-border-radius: 0 0 6px 6px; border-radius: 0 0 6px 6px; - -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); - -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); - -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); -webkit-background-clip: padding-box; -moz-background-clip: padding-box; background-clip: padding-box; } -.openerp .oe_systray .oe_systray_dropdown_options li { +.openerp .oe_topbar .oe_dropdown_menu li { float: none; - display: block; - background-color: none; + padding: 3px 12px; } -.openerp .oe_systray .oe_systray_dropdown_options li a { - display: block; - padding: 4px 15px; - clear: both; - font-weight: normal; - line-height: 18px; +.openerp .oe_topbar .oe_dropdown_menu li a { color: #eeeeee; } -.openerp .oe_systray .oe_systray_dropdown_options li a:hover { +.openerp .oe_topbar .oe_dropdown_menu li:hover { background-color: #292929; background-image: -webkit-gradient(linear, left top, left bottom, from(#292929), to(#191919)); background-image: -webkit-linear-gradient(top, #292929, #191919); @@ -686,62 +1041,114 @@ background-image: linear-gradient(to bottom, #292929, #191919); -moz-box-shadow: none; -webkit-box-shadow: none; - -box-shadow: none; + box-shadow: none; } .openerp .oe_menu { float: left; padding: 0; margin: 0; + font-size: 13px; } -.openerp .oe_menu li { +.openerp .oe_menu > li { + list-style-type: none; + padding: 0; + margin: 0; float: left; + display: block; + color: #eeeeee; } -.openerp .oe_menu a { +.openerp .oe_menu > li > a { display: block; padding: 5px 10px 7px; line-height: 20px; height: 20px; + text-decoration: none; color: #eeeeee; vertical-align: top; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); -} -.openerp .oe_menu a:hover { - background: #303030; + -webkit-transition: all 0.2s ease-out; + -moz-transition: all 0.2s ease-out; + -ms-transition: all 0.2s ease-out; + -o-transition: all 0.2s ease-out; + transition: all 0.2s ease-out; +} +.openerp .oe_menu > li > a:hover { + background: rgba(0, 0, 0, 0.2); + text-shadow: black 0px 0px 3px; color: white; - -moz-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; - -webkit-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; - -box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset; } -.openerp .oe_menu .oe_active { - background: #303030; +.openerp .oe_menu > li > .oe_active { + background: rgba(0, 0, 0, 0.2); + text-shadow: black 0px 0px 3px; font-weight: bold; - color: white; - -moz-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; - -webkit-box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; - -box-shadow: 0 1px 2px rgba(255, 255, 255, 0.3) inset; -} -.openerp .oe_menu_more_container { - position: relative; + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4) inset; } -.openerp .oe_menu_more_container .oe_menu_more { - position: absolute; +.openerp .oe_user_menu { + float: right; padding: 0; - background-color: #646060; - z-index: 1; - border: 1px solid black; - border-bottom-left-radius: 5px; - border-bottom-right-radius: 5px; + margin: 0; } -.openerp .oe_menu_more_container .oe_menu_more li { - float: none; +.openerp .oe_user_menu li { + list-style-type: none; + float: left; } -.openerp .oe_menu_more_container .oe_menu_more li a { - white-space: nowrap; +.openerp .oe_user_menu .oe_dropdown_menu { + right: -1px; +} +.openerp .oe_systray > div { + float: left; + padding: 0 4px 0 4px; +} +.openerp .oe_systray { + float: right; +} +.openerp .oe_leftbar { + display: none; + width: 220px; + background: #f0eeee; + border-right: 1px solid #afafb6; + text-shadow: 0 1px 1px white; + padding-bottom: 16px; +} +.openerp a.oe_logo { + width: 220px; + display: block; + text-align: center; + height: 70px; + line-height: 70px; +} +.openerp a.oe_logo img { + height: 40px; + width: 157px; + margin: 14px 0; +} +.openerp .oe_footer { + position: fixed; + bottom: 0; + padding: 4px 0; + background: #f0eeee; + width: 220px; + text-align: center; +} +.openerp .oe_footer a { + font-weight: 800; + font-family: serif; + font-size: 16px; + color: black; +} +.openerp .oe_footer a span { + color: #c81010; + font-style: italic; } .openerp .oe_secondary_menu_section { font-weight: bold; margin-left: 8px; - color: #8a89ba; + color: #7c7bad; } .openerp .oe_secondary_submenu { padding: 2px 0 8px 0; @@ -749,69 +1156,76 @@ width: 100%; display: inline-block; } -.openerp .oe_secondary_submenu > li { +.openerp .oe_secondary_submenu li { position: relative; - padding: 1px 0 1px 20px; + margin: 0; + padding: 1px 0 1px 20px !important; + list-style-type: none; } -.openerp .oe_secondary_submenu > li a { +.openerp .oe_secondary_submenu li a { display: block; color: #4c4c4c; padding: 2px 4px 2px 0; } -.openerp .oe_secondary_submenu > li .oe_menu_label { +.openerp .oe_secondary_submenu li .oe_menu_label { position: absolute; top: 1px; right: 1px; font-size: 10px; - background: #8a89ba; + background: #7c7bad; color: white; padding: 2px 4px; margin: 1px 6px 0 0; - border: 1px solid lightGray; + border: 1px solid lightgrey; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.2); -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.2); - -box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.2); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.2); } .openerp .oe_secondary_submenu .oe_menu_counter { float: right; - background: #8a89ba; - color: #eeeeee; - font-size: 12px; - border: 1px solid lightgray; - padding: 0px 4px 0px 4px; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); - margin: 0px; - -moz-border-radius: 4px; - -webkit-border-radius: 4px; - border-radius: 4px; + margin: 1px; + padding: 1px 4px; + border: none; +} +.openerp .oe_secondary_submenu .oe_menu_counter:hover { + cursor: pointer; + -webkit-transform: scale(1.1); + -moz-transform: scale(1.1); + -ms-transform: scale(1.1); + -o-transform: scale(1.1); + transform: scale(1.1); } .openerp .oe_secondary_submenu .oe_active { - background: #8a89ba; - border-top: 1px solid lightGray; - border-bottom: 1px solid lightGray; + border-top: 1px solid lightgrey; + border-bottom: 1px solid #dedede; text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); - -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.2); - -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.2); - -box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.2); + -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2), inset 0 -1px 3px rgba(40, 40, 40, 0.2); + -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2), inset 0 -1px 3px rgba(40, 40, 40, 0.2); + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2), inset 0 -1px 3px rgba(40, 40, 40, 0.2); + background: #7c7bad; } .openerp .oe_secondary_submenu .oe_active a { color: white; } .openerp .oe_secondary_submenu .oe_active .oe_menu_label { background: #eeeeee; - color: #8a89ba; + color: #7c7bad; text-shadow: 0 1px 1px white; -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); - -box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2); } .openerp .oe_secondary_submenu .oe_active .oe_menu_counter { background: #eeeeee; - color: #8a89ba; + color: #7c7bad; + -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2); + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2); } .openerp .oe_secondary_submenu .oe_menu_toggler:before { width: 0; @@ -837,190 +1251,153 @@ border-right: 4px solid transparent; border-top: 4px solid #4c4c4c; } -.openerp .oe_application { - width: 100%; +.openerp .oe_about { + background-color: white; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAKUlEQVQIHWO8e/fufwYsgAUkJigoiCIF5DMyoYggcUiXgNnBiGQKmAkARpcEQeriln4AAAAASUVORK5CYII=); + -moz-border-radius: 0 0 2px 2px; + -webkit-border-radius: 0 0 2px 2px; + border-radius: 0 0 2px 2px; } -.openerp .oe_application a { - color: #8a89ba; +.openerp .oe_about a { + color: #7c7bad; } -.openerp .oe_application a:hover { +.openerp .oe_about a:hover { text-decoration: underline; } -.openerp .oe-view-manager-header { - border-top: 1px solid #cacaca; - border-bottom: 1px solid #cacaca; - background-color: #fcfcfc; - background-image: -webkit-gradient(linear, left top, left bottom, from(#fcfcfc), to(#dedede)); - background-image: -webkit-linear-gradient(top, #fcfcfc, #dedede); - background-image: -moz-linear-gradient(top, #fcfcfc, #dedede); - background-image: -ms-linear-gradient(top, #fcfcfc, #dedede); - background-image: -o-linear-gradient(top, #fcfcfc, #dedede); - background-image: linear-gradient(to bottom, #fcfcfc, #dedede); - -moz-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 9px rgba(0, 0, 0, 0.1); - -webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 9px rgba(0, 0, 0, 0.1); - -box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 9px rgba(0, 0, 0, 0.1); -} -.openerp .oe-view-manager-header h2 { - float: left; - font-size: 18px; - margin: 1px 0; -} -.openerp .oe-view-manager-header h2 a { - color: #8a89ba; -} -.openerp .oe-view-manager-header .buttons { - padding-right: 8px; +.openerp .oe_about a:focus { + outline: none; } -.openerp .oe-view-manager-header .buttons li { - float: left; - margin-right: 12px; +.openerp .oe_about .oe_logo { + margin-left: -6px; } -.openerp .oe-view-manager-header .filter { - vertical-align: top; +.openerp .oe_about .oe_bottom { + position: absolute; + top: 50%; + left: 0; + right: 0; + bottom: 0; + text-shadow: 0 1px 1px #999999; + background-color: #b41616; + background-image: -webkit-gradient(linear, left top, left bottom, from(#b41616), to(#600606)); + background-image: -webkit-linear-gradient(top, #b41616, #600606); + background-image: -moz-linear-gradient(top, #b41616, #600606); + background-image: -ms-linear-gradient(top, #b41616, #600606); + background-image: -o-linear-gradient(top, #b41616, #600606); + background-image: linear-gradient(to bottom, #b41616, #600606); + color: #eeeeee; + padding: 0 16px; + -moz-border-radius: 0 0 2px 2px; + -webkit-border-radius: 0 0 2px 2px; + border-radius: 0 0 2px 2px; } -.openerp .oe-view-manager-header .filter li { - height: 24px; - line-height: 24px; - padding: 0 8px; +.openerp .oe_about .oe_bottom a { + color: #eeeeee; } -.openerp .oe-view-manager-header .header-row { - height: 26px; - line-height: 26px; - margin: 8px; - clear: both; - text-shadow: 0 1px 1px white; +.openerp .oe_application { + width: 100%; } -.openerp .oe-view-manager-header .header-row .dropdown-toggle:after { - margin-top: 7px; - margin-left: 6px; - border-top-color: #404040; +.openerp .oe_application a { + color: #7c7bad; } -.openerp .oe-view-manager-header .oe_vm_switch li { - padding-left: 1px; - text-align: center; - width: 24px; - height: 24px; - line-height: 24px; +.openerp .oe_application a:hover { + text-decoration: underline; } -.openerp .oe-view-manager-header .pagination li { - height: 24px; - line-height: 24px; - padding: 0 8px; +.openerp .oe_application > div { + height: 100%; } -.openerp .oe-view-manager-header .button-group { +.openerp .oe_application .oe_breadcrumb_item:not(:last-child) { display: inline-block; - border: 1px solid #ababab; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; - border-radius: 5px; -} -.openerp .oe-view-manager-header .button-group li { - float: left; - border-right: 1px solid #ababab; -} -.openerp .oe-view-manager-header .button-group li:last-child { - border: none; + max-width: 7em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } -.openerp .oe-view-manager-header .button-group a { - color: #4c4c4c; +.openerp .oe_view_manager .oe_view_manager_body { + height: inherit; } -.openerp .oe-view-manager-header .button-group a:hover { - text-decoration: none; +.openerp .oe_view_manager .oe_view_manager_view_kanban { + height: inherit; } -.openerp .oe-view-manager-header .button-group .active { +.openerp .oe_view_manager table.oe_view_manager_header { width: 100%; - background: #999999; - -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; - -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; - -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; - color: white; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); + table-layout: fixed; } -.openerp .oe_view_manager_header { - width: 100%; - border-top: 1px solid #cacaca; - border-bottom: 1px solid #cacaca; - background-color: #fcfcfc; - background-image: -webkit-gradient(linear, left top, left bottom, from(#fcfcfc), to(#dedede)); - background-image: -webkit-linear-gradient(top, #fcfcfc, #dedede); - background-image: -moz-linear-gradient(top, #fcfcfc, #dedede); - background-image: -ms-linear-gradient(top, #fcfcfc, #dedede); - background-image: -o-linear-gradient(top, #fcfcfc, #dedede); - background-image: linear-gradient(to bottom, #fcfcfc, #dedede); - -moz-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 9px rgba(0, 0, 0, 0.1); - -webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 9px rgba(0, 0, 0, 0.1); - -box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 9px rgba(0, 0, 0, 0.1); -} -.openerp .oe_view_manager_header .oe_header_row { +.openerp .oe_view_manager table.oe_view_manager_header .oe_header_row { clear: both; text-shadow: 0 1px 1px white; } -.openerp .oe_view_manager_header .oe_header_row td { - padding: 8px; -} -.openerp .oe_view_manager_header .oe_header_row:last-child td { +.openerp .oe_view_manager table.oe_view_manager_header .oe_header_row:last-child td { padding-top: 0; } -.openerp .oe_view_manager_header .oe_header_row:first-child td { +.openerp .oe_view_manager table.oe_view_manager_header .oe_header_row:first-child td { padding-top: 8px; } -.openerp .oe_view_manager_header .oe_view_manager_sidebar { +.openerp .oe_view_manager table.oe_view_manager_header .oe_view_manager_sidebar { margin: 0px auto; - width: 400px; text-align: center; } -.openerp .oe_view_manager_header td { +.openerp .oe_view_manager table.oe_view_manager_header td { line-height: 26px; } -.openerp .oe_view_manager_header h2 { +.openerp .oe_view_manager table.oe_view_manager_header h2 { font-size: 18px; margin: 0; float: left; } -.openerp .oe_view_manager_header h2 a { - color: #8a89ba; +.openerp .oe_view_manager table.oe_view_manager_header h2 a { + color: #7c7bad; +} +.openerp .oe_view_manager table.oe_view_manager_header .oe_dropdown_menu { + line-height: normal; } -.openerp .oe_view_manager_header .oe_button_group { +.openerp .oe_view_manager table.oe_view_manager_header .oe_button_group { display: inline-block; border: 1px solid #ababab; -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; } -.openerp .oe_view_manager_header .oe_button_group li { +.openerp .oe_view_manager table.oe_view_manager_header .oe_button_group li { float: left; border-right: 1px solid #ababab; } -.openerp .oe_view_manager_header .oe_button_group li:last-child { +.openerp .oe_view_manager table.oe_view_manager_header .oe_button_group li:last-child { border: none; } -.openerp .oe_view_manager_header .oe_button_group a { +.openerp .oe_view_manager table.oe_view_manager_header .oe_button_group a { color: #4c4c4c; } -.openerp .oe_view_manager_header .oe_button_group a:hover { +.openerp .oe_view_manager table.oe_view_manager_header .oe_button_group a:hover { text-decoration: none; } -.openerp .oe_view_manager_header .oe_button_group .active { +.openerp .oe_view_manager table.oe_view_manager_header .oe_button_group .active { background: #999999; -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; - -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; } -.openerp .oe_view_manager_header .oe_button_group .active a { +.openerp .oe_view_manager table.oe_view_manager_header .oe_button_group .active a { color: white; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); } -.openerp .oe_view_manager_header .oe_view_manager_buttons { +.openerp .oe_view_manager table.oe_view_manager_header .oe_view_manager_buttons { white-space: nowrap; } -.openerp .oe_view_manager_pager { +.openerp .oe_view_manager .oe_view_manager_pager { line-height: 26px; } -.openerp .oe_pager_value { +.openerp .oe_view_manager .oe_view_manager_pager .oe_list_pager_single_page .oe_pager_group { + display: none; +} +.openerp .oe_view_manager .oe_pager_value { float: left; margin-right: 8px; } -.openerp .oe_pager_group { +.openerp .oe_view_manager ul.oe_pager_group { + padding: 0; + margin: 0; +} +.openerp .oe_view_manager .oe_pager_group { float: left; height: 24px; line-height: 24px; @@ -1031,47 +1408,59 @@ -webkit-border-radius: 5px; border-radius: 5px; } -.openerp .oe_pager_group li { +.openerp .oe_view_manager .oe_pager_group li { height: 24px; line-height: 24px; padding: 0; + margin: 0; + list-style-type: none; float: left; border-right: 1px solid #ababab; } -.openerp .oe_pager_group li:last-child { +.openerp .oe_view_manager .oe_pager_group li:last-child { border: none; } -.openerp .oe_pager_group a { +.openerp .oe_view_manager .oe_pager_group a { color: #4c4c4c; padding: 0 8px; } -.openerp .oe_pager_group a:hover { +.openerp .oe_view_manager .oe_pager_group a:hover { text-decoration: none; } -.openerp .oe_pager_group .active { +.openerp .oe_view_manager .oe_pager_group .active { background: #999999; -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; - -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3) inset; } -.openerp .oe_pager_group .active a { +.openerp .oe_view_manager .oe_pager_group .active a { color: white; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.4); } -.openerp .oe_view_manager_switch li { - text-align: center; +.openerp .oe_view_manager .oe_view_manager_switch { + padding: 0; + margin: 0 0 0 8px; +} +.openerp .oe_view_manager .oe_view_manager_switch li { + margin: 0; width: 24px; height: 24px; line-height: 16px; + padding: 0; + text-align: center; + list-style-type: none; } -.openerp .oe_view_manager_switch li a { +.openerp .oe_view_manager .oe_view_manager_switch li a { position: relative; } -.openerp .oe_view_manager_switch .oe_vm_switch_list:after, .openerp .oe_view_manager_switch .oe_vm_switch_tree:after { +.openerp .oe_view_manager .oe_view_manager_switch .oe_vm_switch_list:after, .openerp .oe_view_manager .oe_view_manager_switch .oe_vm_switch_tree:after { padding: 2px; content: "i"; } -.openerp .oe_view_manager_switch .oe_vm_switch_graph:after { +.openerp .oe_view_manager .oe_view_manager_switch .oe_vm_switch_form:after { + content: "m"; +} +.openerp .oe_view_manager .oe_view_manager_switch .oe_vm_switch_graph:after { font-family: "mnmliconsRegular" !important; font-size: 21px; font-weight: 300 !important; @@ -1079,7 +1468,7 @@ top: -2px; position: relative; } -.openerp .oe_view_manager_switch .oe_vm_switch_gantt:after { +.openerp .oe_view_manager .oe_view_manager_switch .oe_vm_switch_gantt:after { font-family: "mnmliconsRegular" !important; font-size: 21px; font-weight: 300 !important; @@ -1087,85 +1476,45 @@ top: -2px; position: relative; } -.openerp .oe_view_manager_switch .oe_vm_switch_calendar:after { +.openerp .oe_view_manager .oe_view_manager_switch .oe_vm_switch_calendar:after { content: "P"; } -.openerp .oe_view_manager_switch .oe_vm_switch_kanban:after { +.openerp .oe_view_manager .oe_view_manager_switch .oe_vm_switch_kanban:after { content: "k"; } -.openerp .oe_view_manager_switch .oe_vm_switch_diagram:after { +.openerp .oe_view_manager .oe_view_manager_switch .oe_vm_switch_diagram:after { content: "f"; } -.openerp .oe_form_dropdown_section { - position: relative; - display: inline-block; +.openerp .oe_view_manager_current { + height: 100%; } -.openerp .oe_dropdown_toggle:after { - width: 0; - height: 0; - display: inline-block; - content: "&darr"; - text-indent: -99999px; - vertical-align: top; - border-left: 4px solid transparent; - border-right: 4px solid transparent; - border-top: 4px solid white; - filter: alpha(opacity=50); - opacity: 0.5; - margin-top: 7px; - margin-left: 6px; - border-top-color: #404040; +.openerp .oe_view_manager_current > .oe_view_manager_header { + border-bottom: 1px solid #cacaca; + background-color: #fcfcfc; + background-image: -webkit-gradient(linear, left top, left bottom, from(#fcfcfc), to(#dedede)); + background-image: -webkit-linear-gradient(top, #fcfcfc, #dedede); + background-image: -moz-linear-gradient(top, #fcfcfc, #dedede); + background-image: -ms-linear-gradient(top, #fcfcfc, #dedede); + background-image: -o-linear-gradient(top, #fcfcfc, #dedede); + background-image: linear-gradient(to bottom, #fcfcfc, #dedede); + -moz-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 9px rgba(0, 0, 0, 0.1); + -webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 9px rgba(0, 0, 0, 0.1); + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4), 0 0 9px rgba(0, 0, 0, 0.1); } -.openerp .oe_dropdown_menu { - display: none; - position: absolute; - top: 28px; - left: 0px; +.openerp .oe_view_manager_current > .oe_view_manager_header .oe_header_row td { padding: 8px; - border: 1px solid #afafb6; - min-width: 120px; - overflow-x: hidden; - z-index: 900; - text-align: left; - background: white; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; - -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); - -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); - -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); } -.openerp .oe_dropdown_menu li { - list-style-type: none; - float: none; - display: block; - background-color: none; +.openerp .oe_view_manager_current > .oe_view_manager_header .oe_header_row:first-child td { + padding-top: 8px; } -.openerp .oe_dropdown_menu li a { - display: block; - padding: 3px 6px; - clear: both; - font-weight: normal; - line-height: 14px; - color: #4c4c4c; - text-decoration: none; - cursor: pointer; +.openerp .oe_view_manager_inline, .openerp .oe_view_manager_inlineview { + height: 100%; } -.openerp .oe_dropdown_menu li a:hover { - text-decoration: none; - background-color: #f0f0fa; - background-image: -webkit-gradient(linear, left top, left bottom, from(#f0f0fa), to(#eeeef6)); - background-image: -webkit-linear-gradient(top, #f0f0fa, #eeeef6); - background-image: -moz-linear-gradient(top, #f0f0fa, #eeeef6); - background-image: -ms-linear-gradient(top, #f0f0fa, #eeeef6); - background-image: -o-linear-gradient(top, #f0f0fa, #eeeef6); - background-image: linear-gradient(to bottom, #f0f0fa, #eeeef6); - -moz-box-shadow: none; - -webkit-box-shadow: none; - -box-shadow: none; +.openerp .oe_view_manager_inline > .oe_view_manager_header, .openerp .oe_view_manager_inlineview > .oe_view_manager_header { + display: none; } -.openerp .oe_sidebar { - white-space: nowrap; +.openerp .oe_popup_form > .oe_formview > .oe_form_pager { + display: none !important; } .openerp .oe_searchview { cursor: text; @@ -1173,7 +1522,7 @@ float: right; padding: 1px 0; line-height: 18px; - width: 480px; + width: 400px; border: 1px solid #ababab; background: white; -moz-border-radius: 13px; @@ -1181,23 +1530,19 @@ border-radius: 13px; -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2) inset; -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2) inset; - -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2) inset; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2) inset; } .openerp .oe_searchview input, .openerp .oe_searchview textarea { padding: 3px; height: 14px; - font-size: 11px; + font-size: 12px; line-height: 18px; - border: 1px solid #cccccc; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; } .openerp .oe_searchview.oe_focused { border-color: #a6a6fe; -moz-box-shadow: 0 1px 2px #a6a6fe inset; -webkit-box-shadow: 0 1px 2px #a6a6fe inset; - -box-shadow: 0 1px 2px #a6a6fe inset; + box-shadow: 0 1px 2px #a6a6fe inset; } .openerp .oe_searchview .oe_searchview_clear { cursor: pointer; @@ -1235,52 +1580,66 @@ filter: alpha(opacity=50); opacity: 0.5; } -.openerp .oe_searchview .oe_searchview_facets { - min-height: 22px; +.openerp .oe_searchview .oe_searchview_search { + font-size: 1px; + letter-spacing: -1px; + color: transparent; + font-weight: normal; + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; + position: absolute; + left: 3px; + top: 1px; + padding: 0; + border: none; + background: transparent; } -.openerp .oe_searchview .oe_searchview_facets:before { - color: #cccccc; - font-family: "mnmliconsRegular"; +.openerp .oe_searchview .oe_searchview_search:before { + font: 21px "mnmliconsRegular"; content: "r"; - font-size: 130%; - display: inline; - position: relative; - left: 6px; - top: 2px; color: #a3a3a3; - padding-right: 4px; +} +.openerp .oe_searchview .oe_searchview_facets { + min-height: 22px; + margin-left: 15px; } .openerp .oe_searchview .oe_searchview_facets * { vertical-align: top; display: inline-block; line-height: 17px; } -.openerp .oe_searchview .oe_searchview_facets .oe_searchview_input, .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet { +.openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet { height: 18px; margin: 1px 0; font-size: 11px; } -.openerp .oe_searchview .oe_searchview_facets .oe_searchview_input:focus, .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet:focus { +.openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet:focus { outline: none; } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_input { - padding: 0 3px; + padding: 0 0 0 6px; + font-size: 12px; + height: 16px; + margin-top: 3px; +} +.openerp .oe_searchview .oe_searchview_facets .oe_searchview_input:focus { + outline: none; } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet { position: relative; cursor: pointer; - border: 1px solid #afafb6; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; - background: #8786b7; + padding: 0; -webkit-font-smoothing: auto; } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet:focus { border-color: #a6a6fe; -moz-box-shadow: 0 0 3px 1px #a6a6fe; -webkit-box-shadow: 0 0 3px 1px #a6a6fe; - -box-shadow: 0 0 3px 1px #a6a6fe; + box-shadow: 0 0 3px 1px #a6a6fe; } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_values { background: #f0f0fa; @@ -1302,6 +1661,7 @@ .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_value { border-left: 1px solid #afafb6; text-shadow: 0 1px 1px white; + color: #4c4c4c; } .openerp .oe_searchview .oe_searchview_facets .oe_searchview_facet .oe_facet_value:last-child { padding-right: 16px; @@ -1314,6 +1674,7 @@ line-height: 8px; width: 12px; height: 12px; + padding-top: 1px; text-align: center; font-weight: bold; cursor: pointer; @@ -1332,7 +1693,7 @@ } .openerp .oe_searchview .oe_searchview_drawer { position: absolute; - z-index: 1; + z-index: 100; margin-top: 4px; top: 100%; right: -1px; @@ -1346,7 +1707,7 @@ border-radius: 4px; -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); - -box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); } .openerp .oe_searchview .oe_searchview_drawer > div { border-top: 1px solid #cccccc; @@ -1359,7 +1720,7 @@ } .openerp .oe_searchview .oe_searchview_drawer h3 { margin: 8px 4px 4px 12px; - color: #8786b7; + color: #7c7bad; font-size: 13px; } .openerp .oe_searchview .oe_searchview_drawer h4, .openerp .oe_searchview .oe_searchview_drawer h4 * { @@ -1384,7 +1745,7 @@ padding: 0 2px; -moz-box-shadow: none; -webkit-box-shadow: none; - -box-shadow: none; + box-shadow: none; -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; @@ -1394,8 +1755,9 @@ width: 100%; } .openerp .oe_searchview .oe_searchview_drawer .oe_searchview_section > div { - -moz-box-sizing: border-box; -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; box-sizing: border-box; display: table-cell; width: 50%; @@ -1426,14 +1788,14 @@ .openerp .oe_searchview .oe_searchview_drawer .oe_searchview_section li:hover { background-color: #f0f0fa; } -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_section form { +.openerp .oe_searchview .oe_searchview_drawer form { margin-left: 12px; } -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_section form p { +.openerp .oe_searchview .oe_searchview_drawer form p { margin: 4px 0; line-height: 18px; } -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_section form button { +.openerp .oe_searchview .oe_searchview_drawer form button { margin: 0 0 8px 0; } .openerp .oe_searchview .oe_searchview_drawer .oe_searchview_custom { @@ -1456,6 +1818,10 @@ top: 0; right: 5px; } +.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_dashboard form { + display: none; + margin-top: 2px; +} .openerp .oe_searchview .oe_searchview_drawer .oe_searchview_advanced form { display: none; margin-top: 8px; @@ -1485,14 +1851,12 @@ .openerp .oe_searchview .oe_searchview_drawer .oe_opened form { display: block; } -.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_custom_delete { - position: absolute; - left: 4px; - top: 3px; -} .openerp .oe_searchview .oe_searchview_drawer .oe_searchview_custom_delete, .openerp .oe_searchview .oe_searchview_drawer .searchview_extended_delete_prop { + display: inline-block; width: 12px; height: 12px; + line-height: 12px; + padding: 1px; color: #8786b7; line-height: 8px; text-align: center; @@ -1508,55 +1872,147 @@ -webkit-border-radius: 2px; border-radius: 2px; } -.openerp .oe_view_nocontent > img { - float: left; - margin: 1.5em; +.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_custom_delete { + display: none; + position: absolute; + bottom: 1px; + right: 4px; } -.openerp .oe_view_nocontent > div { - overflow: hidden; - padding: 35px 0px 0px 0px; - max-width: 700px; +.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_custom_private:hover .oe_searchview_custom_delete, .openerp .oe_searchview .oe_searchview_drawer .oe_searchview_custom_public:hover .oe_searchview_custom_delete { + display: inline-block; +} +.openerp .oe_searchview .oe_searchview_drawer .oe_searchview_custom_public:after { + content: ","; + font-family: "entypoRegular" !important; + font-size: 22px; + font-weight: 300 !important; + margin: 0 0 0 4px; + padding: 0; +} +.openerp .oe_view_nocontent { + padding: 15px; + margin-top: 0; + color: #777777; font-size: 125%; + max-width: 700px; } -.openerp .oe_view_topbar { - border-bottom: 1px solid #cacaca; - background-color: #fcfcfc; - background-image: -webkit-gradient(linear, left top, left bottom, from(#fcfcfc), to(#dedede)); - background-image: -webkit-linear-gradient(top, #fcfcfc, #dedede); - background-image: -moz-linear-gradient(top, #fcfcfc, #dedede); - background-image: -ms-linear-gradient(top, #fcfcfc, #dedede); - background-image: -o-linear-gradient(top, #fcfcfc, #dedede); - background-image: linear-gradient(to bottom, #fcfcfc, #dedede); - padding: 0 8px; - line-height: 30px; +.openerp .oe_view_nocontent .oe_view_nocontent_create { + background: transparent url(/web/static/src/img/view_empty_arrow.png) no-repeat 7px 0; + margin-top: 0; + padding-top: 35px; + min-height: 28px; + color: #4c4c4c; +} +.openerp .oe_view_nocontent > p { + padding-left: 95px; +} +.openerp .oe_view_nocontent .oe_empty_custom_dashboard { + background: transparent url(/web/static/src/img/graph_background.png) no-repeat 0 0; + margin-top: -15px; + padding: 100px 0 0 137px; + min-height: 327px; + margin-left: -15px; } .openerp .oe_formview { background: white; } -.openerp .oe_form_header { - padding: 8px; +.openerp .oe_form_dropdown_section { + position: relative; + display: inline-block; } .openerp .oe_form_invalid input, .openerp .oe_form_invalid select, .openerp .oe_form_invalid textarea { background-color: #ff6666 !important; border: 1px solid #dd0000 !important; } -.openerp .oe_form_button_save_dirty { - color: white; - background: #dc5f59; - background: -moz-linear-gradient(#dc5f59, #b33630); - background: -webkit-gradient(linear, left top, left bottom, from(#dc5f59), to(#b33630)); - background: -webkit-linear-gradient(#dc5f59, #b33630); +.openerp .oe_view_manager_current .oe_form_editable .oe_highlight { + color: #404040; + background: none; +} +.openerp .oe_view_manager_current .oe_form_editable button.oe_highlight { + background-color: #efefef; + background-image: -webkit-gradient(linear, left top, left bottom, from(#efefef), to(#d8d8d8)); + background-image: -webkit-linear-gradient(top, #efefef, #d8d8d8); + background-image: -moz-linear-gradient(top, #efefef, #d8d8d8); + background-image: -ms-linear-gradient(top, #efefef, #d8d8d8); + background-image: -o-linear-gradient(top, #efefef, #d8d8d8); + background-image: linear-gradient(to bottom, #efefef, #d8d8d8); + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset; +} +.openerp .oe_view_manager_current .oe_form_editable button.oe_highlight:active { + background-color: #e3e3e3; + background-image: -webkit-gradient(linear, left top, left bottom, from(#e3e3e3), to(#f6f6f6)); + background-image: -webkit-linear-gradient(top, #e3e3e3, #f6f6f6); + background-image: -moz-linear-gradient(top, #e3e3e3, #f6f6f6); + background-image: -ms-linear-gradient(top, #e3e3e3, #f6f6f6); + background-image: -o-linear-gradient(top, #e3e3e3, #f6f6f6); + background-image: linear-gradient(to bottom, #e3e3e3, #f6f6f6); -moz-box-shadow: none; -webkit-box-shadow: none; - -box-shadow: none; - font-weight: bold; + box-shadow: none; } -.openerp .oe_form_button_save_dirty:hover { - background: #ed6f6a; +.openerp .oe_view_manager_current .oe_form_editable button.oe_highlight:hover { + background-color: #f6f6f6; + background-image: -webkit-gradient(linear, left top, left bottom, from(#f6f6f6), to(#e3e3e3)); + background-image: -webkit-linear-gradient(top, #f6f6f6, #e3e3e3); + background-image: -moz-linear-gradient(top, #f6f6f6, #e3e3e3); + background-image: -ms-linear-gradient(top, #f6f6f6, #e3e3e3); + background-image: -o-linear-gradient(top, #f6f6f6, #e3e3e3); + background-image: linear-gradient(to bottom, #f6f6f6, #e3e3e3); + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(255, 255, 255, 0.8) inset; +} +.openerp .oe_form_invisible { + display: none !important; +} +.openerp .oe_form_readonly .oe_edit_only, .openerp .oe_form_readonly .oe_form_field:empty { + display: none !important; +} +.openerp .oe_form_readonly .oe_form .oe_form_field_date { + width: auto; +} +.openerp .oe_form_nosheet { + margin: 16px; +} +.openerp .oe_form_nosheet > header { + margin: -16px -16px 0 -16px; + padding: 0; +} +.openerp .oe_form_sheetbg { + padding: 16px 0; +} +.openerp .oe_form_sheet_width { + min-width: 650px; + max-width: 860px; + margin: 0 auto; +} +.openerp .oe_form_sheet { + background: white; + min-height: 330px; + padding: 16px; +} +.openerp .oe_application .oe_form_sheetbg { + background: url(/web/static/src/img/form_sheetbg.png); + border-bottom: 1px solid #dddddd; +} +.openerp .oe_application .oe_form_sheet { + border: 1px solid #c8c8d3; + -moz-box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + -webkit-box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); +} +.openerp .oe_application .oe_form_sheet .ui-tabs { + margin: 0 -16px; +} +.openerp .oe_application .oe_form_sheet .oe_notebook_page { + padding: 0 16px; } .openerp .oe_form header { position: relative; border-bottom: 1px solid #cacaca; + padding-left: 2px; background-color: #fcfcfc; background-image: -webkit-gradient(linear, left top, left bottom, from(#fcfcfc), to(#dedede)); background-image: -webkit-linear-gradient(top, #fcfcfc, #dedede); @@ -1564,146 +2020,95 @@ background-image: -ms-linear-gradient(top, #fcfcfc, #dedede); background-image: -o-linear-gradient(top, #fcfcfc, #dedede); background-image: linear-gradient(to bottom, #fcfcfc, #dedede); - padding: 0 8px; - line-height: 30px; -} -.openerp .oe_form header button { - font-size: 12px; - float: left; - height: 24px !important; - line-height: 24px; - vertical-align: top; - padding: 0 10px; - margin: 3px 4px 3px 0; } -.openerp .oe_form header button span.i { - line-height: 20px; - height: 24px; +.openerp .oe_form header > span { + margin-left: 4px; } .openerp .oe_form header ul { - height: 30px; - padding: 0; - margin: 0; - text-shadow: 0 1px 1px white; - border-left: 1px solid #cacaca; - border-right: 1px solid #cacaca; -} -.openerp .oe_form header ul li { - padding: 0; - margin: 0; - float: left; - vertical-align: top; - border-right: 1px solid #cacaca; - height: 30px; - padding: 0 12px; -} -.openerp .oe_form header ul li:first-child { - border-left: 1px solid #cacaca; -} -.openerp .oe_form header ul li a { - color: #4c4c4c; -} -.openerp .oe_form header ul li a:hover { - color: black; -} -.openerp .oe_form header .oe_form_steps { display: inline-block; float: right; } -.openerp .oe_form header .oe_form_steps img { - margin: 0 8px; - vertical-align: top; +.openerp .oe_form header .oe_form_button { + margin: 3px 2px 1px; } -.openerp .oe_form header .oe_form_steps li { - border-right: none; - padding: 0; +.openerp .oe_form header .oe_tags { + margin: 5px 0 0 5px; + width: 400px; + padding-bottom: 0; } -.openerp .oe_form header .oe_form_steps li:first-child { - margin-left: 12px; - border-left: none; +.openerp .oe_form div.oe_chatter { + min-width: 650px; + max-width: 860px; + margin: 0 auto; + padding: 16px 0 48px; } -.openerp .oe_form header .oe_form_steps li:last-child { - margin-right: 12px; +.openerp .oe_form .oe_grey { + color: #aaaaaa; + max-width: 650px; + margin: 0 0 10px 0; } -.openerp .oe_form header .oe_form_steps_active { - font-weight: bold; - color: #b33630; +.openerp .oe_form div.oe_form_configuration p, .openerp .oe_form div.oe_form_configuration ul, .openerp .oe_form div.oe_form_configuration ol { + color: #aaaaaa; + max-width: 650px; } -.openerp .oe_form_readonly button.oe_form_button_hi { - color: white; - background: #dc5f59; - background: -moz-linear-gradient(#dc5f59, #b33630); - background: -webkit-gradient(linear, left top, left bottom, from(#dc5f59), to(#b33630)); - background: -webkit-linear-gradient(#dc5f59, #b33630); - -moz-box-shadow: none; - -webkit-box-shadow: none; - -box-shadow: none; +.openerp .oe_form div.oe_form_configuration label { + min-width: 150px; } -.openerp .oe_form_readonly button.oe_form_button_hi:hover { - background: #df6b66; - background: -moz-linear-gradient(#df6b66, #bf3a33); - background: -webkit-gradient(linear, left top, left bottom, from(#df6b66), to(#bf3a33)); - background: -webkit-linear-gradient(#df6b66, #bf3a33); - -moz-box-shadow: 0 0 1px rgba(0, 0, 0, 0.2); - -webkit-box-shadow: 0 0 1px rgba(0, 0, 0, 0.2); - -box-shadow: 0 0 1px rgba(0, 0, 0, 0.2); +.openerp .oe_form div.oe_form_configuration .oe_form_group_cell_label { + padding: 1px 0; } -.openerp .oe_application .oe_form_sheetbg { - background: url(/web/static/src/img/form_sheetbg.png); - padding: 8px 0; - border-bottom: 1px solid #dddddd; +.openerp .oe_form div.oe_form_configuration .oe_form_group_cell div div { + padding: 1px 0; } -.openerp .oe_application .oe_form_sheet_width, .openerp .oe_application footer { - min-width: 650px; - max-width: 960px; - margin: 0 auto; +.openerp .oe_form .oe_subtotal_footer { + width: 1% !important; } -.openerp .oe_application .oe_form_sheet { - padding: 18px; - background: white; - min-height: 330px; - border: 1px solid #afafb6; - -moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); - -webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); - -box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); +.openerp .oe_form .oe_subtotal_footer td.oe_form_group_cell { + text-align: right; + padding: 0 !important; } -.openerp .oe_form_invisible { - display: none; +.openerp .oe_form .oe_subtotal_footer td.oe_form_group_cell_label { + border-right: none; } -.openerp .oe_form_editable .oe_form_editable_hidden { - display: none; +.openerp .oe_form .oe_subtotal_footer .oe_subtotal_footer_separator { + width: 108px; + border-top: 1px solid #cacaca; + margin-top: 4px; + padding-top: 4px; + font-weight: bold; + font-size: 18px; } -.openerp .oe_form_readonly .oe_form_readonly_hidden, .openerp .oe_form_readonly .oe_form_field:empty { - display: none; +.openerp .oe_form .oe_subtotal_footer label:after { + content: ":"; } -.openerp .oe_form .oe_form_field_text { - width: 100%; +.openerp .oe_form .oe_subtotal_footer label.oe_subtotal_footer_separator { + font-weight: bold !important; + padding: 2px 11px 2px 0px !important; } -.openerp .oe_form .oe_form_field_char input, -.openerp .oe_form .oe_form_field_text textarea, -.openerp .oe_form .oe_form_field_url input, -.openerp .oe_form .oe_form_field_selection select { - width: inherit; +.openerp .oe_form .oe_subtotal_footer label.oe_form_label_help { + font-weight: normal; } -.openerp .oe_form .oe_form_field_float input { - width: 100px; +.openerp .oe_form .oe_form_button { + margin: 2px; } -.openerp .oe_form h1 .oe_form_field_float input { - width: 140px; +.openerp .oe_form td.oe_form_group_cell_label { + border-right: 1px solid #dddddd; + padding: 2px 0px; } -.openerp .oe_form h2 .oe_form_field_float input { - width: 120px; +.openerp .oe_form td.oe_form_group_cell_label label { + line-height: 18px; + display: block; + min-width: 140px; } -.openerp .oe_form h1, .openerp .oe_form h2, .openerp .oe_form h3, .openerp .oe_form h4, .openerp .oe_form h5, .openerp .oe_form h6 { - margin: 0 0 4px 0; +.openerp .oe_form td.oe_form_group_cell + .oe_form_group_cell { + padding: 2px 0 2px 8px; } -.openerp .oe_form h1 input, .openerp .oe_form h2 input, .openerp .oe_form h3 input, .openerp .oe_form h4 input, .openerp .oe_form h5 input, .openerp .oe_form h6 input { - height: inherit !important; - font-size: inherit; +.openerp .oe_form .oe_form_group { + width: 100%; + margin: 9px 0 9px 0; } -.openerp .oe_form_nosheet > *:not(.oe_form_topbar) { - margin-left: 10px !important; - margin-right: 10px !important; +.openerp .oe_form .oe_form_group .oe_form_group_cell.oe_group_right { + padding-left: 20px; } .openerp .oe_form .oe_form_label_help[for], .openerp .oe_form .oe_form_label[for] { font-weight: bold; @@ -1712,69 +2117,99 @@ } .openerp .oe_form .oe_form_label_help[for] span, .openerp .oe_form .oe_form_label[for] span { font-size: 80%; - color: darkGreen; + color: darkgreen; vertical-align: top; position: relative; top: -4px; padding: 0 2px; } -.openerp .oe_form .oe_form_field_text textarea { - resize: vertical; +.openerp .oe_horizontal_border { + border-bottom: 1px solid black; } -.openerp .oe_form .oe_form_field { - width: 100%; +.openerp .oe_horizontal_separator { + font-weight: bold; + font-size: 20px; + margin: 15px 0px 10px 0px; + color: #7c7bad; +} +.openerp .oe_horizontal_separator:empty { + height: 5px; +} +.openerp .oe_vertical_separator { + border-left: 1px solid #666666; + padding: 0 4px 0 4px; +} +.openerp .oe_form_field_progressbar { display: inline-block; - padding: 2px 2px 2px 0px; - line-height: 18px; + min-width: 70px; } -.openerp .oe_form .oe_form_field input { - margin: 0px; +.openerp .oe_form_field_progressbar.ui-progressbar { + height: 22px; + font-size: 10px; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + border: 1px solid #999999; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + background: white; + min-width: 50px; } -.openerp .oe_form .oe_form_field_integer { - width: 180px; +.openerp .oe_form_field_progressbar.ui-progressbar span { + position: absolute; + margin-left: 10px; + font-weight: bold; } -.openerp .oe_form .oe_form_field_float { - width: 180px; +.openerp .oe_form_field_progressbar.ui-progressbar .ui-widget-header { + background: #cccccc url(/web/static/lib/jquery.ui/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; +} +.openerp .oe_form .oe_form_field_text { + width: 100%; } -.openerp .oe_form .oe_form_field_date { - width: 7em; +.openerp .oe_form .oe_form_field_char input, +.openerp .oe_form .oe_form_field_url input, +.openerp .oe_form .oe_form_field_email input, +.openerp .oe_form .oe_form_field_text textarea, +.openerp .oe_form .oe_form_field_selection select { + width: 100%; } -.openerp .oe_form .oe_form_field_datetime { - width: 11em; +.openerp .oe_form .oe_form_field_text.oe_inline, .openerp .oe_form .oe_form_field_text.oe_inline > textarea { + width: 500px; } -.openerp .oe_form .oe_form_inline { - width: auto; +.openerp .oe_form h1, .openerp .oe_form h2, .openerp .oe_form h3, .openerp .oe_form h4, .openerp .oe_form h5, .openerp .oe_form h6 { + margin: 0 0 4px 0; } -.openerp .oe_form input[type="text"], -.openerp .oe_form input[type="password"], -.openerp .oe_form input[type="file"], -.openerp .oe_form select { +.openerp .oe_form h1 input, .openerp .oe_form h2 input, .openerp .oe_form h3 input, .openerp .oe_form h4 input, .openerp .oe_form h5 input, .openerp .oe_form h6 input { + height: inherit !important; + font-size: inherit; +} +.openerp .oe_form .oe_form_field { + width: 100%; + display: inline-block; + padding: 2px 2px 2px 0px; +} +.openerp .oe_form .oe_form_field input { + margin: 0px; +} +.openerp .oe_form input[type="text"], .openerp .oe_form input[type="password"], .openerp .oe_form input[type="file"], .openerp .oe_form select { height: 22px; padding-top: 2px; } -.openerp .oe_form input[type="text"], -.openerp .oe_form input[type="password"], -.openerp .oe_form input[type="file"], -.openerp .oe_form select, -.openerp .oe_form textarea { - -moz-box-sizing: border-box; +.openerp .oe_form input[type="text"], .openerp .oe_form input[type="password"], .openerp .oe_form input[type="file"], .openerp .oe_form select, .openerp .oe_form textarea { -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box; - padding: 0 2px; - border: 1px solid #999999; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; background: white; - min-width: 70px; + min-width: 60px; color: #1f1f1f; } -.openerp .oe_form input[readonly], -.openerp .oe_form select[readonly], -.openerp .oe_form textarea[readonly], -.openerp .oe_form input[disabled], -.openerp .oe_form select[disabled] { +.openerp .oe_form textarea { + height: 32px; +} +.openerp .oe_form input[readonly], .openerp .oe_form select[readonly], .openerp .oe_form textarea[readonly], .openerp .oe_form input[disabled], .openerp .oe_form select[disabled] { background: #e5e5e5 !important; color: #666666; } @@ -1784,25 +2219,11 @@ padding-left: 8px; -moz-box-shadow: none; -webkit-box-shadow: none; - -box-shadow: none; + box-shadow: none; -moz-border-radius: 0px; -webkit-border-radius: 0px; border-radius: 0px; } -.openerp .oe_form .oe_form_field_many2one input, -.openerp .oe_form .oe_form_field_binary input, -.openerp .oe_form .oe_form_field_binary input, -.openerp .oe_form .oe_form_field_email input, -.openerp .oe_form .oe_form_field_url input { - border-right: none; - -webkit-border-top-right-radius: 0px; - -webkit-border-bottom-right-radius: 0px; - -moz-border-radius-topright: 0px; - -moz-border-radius-bottomright: 0px; - border-top-right-radius: 0px; - border-bottom-right-radius: 0px; -} -.openerp .oe_form .oe_form_field_email button img, .openerp .oe_form .oe_form_field_url button img { vertical-align: top; } @@ -1812,29 +2233,7 @@ } .openerp .oe_form .oe_form_field_boolean { padding-top: 4px; -} -.openerp .oe_form .oe_form_field_many2manytags .text-wrap { - width: 100% !important; -} -.openerp .oe_form .oe_form_field_many2manytags .text-wrap textarea { - width: 100% !important; -} -.openerp .oe_form .oe_form_field_many2manytags .oe_form_field_many2manytags_box { - border-radius: 2px; - box-sizing: border-box; - position: relative; - float: left; - border: 1px solid #9daccc; - background: #e2e6f0; - color: black; - padding: 0px 3px 0px 3px; - margin: 0 2px 2px 0; - height: 16px; - font: 11px "lucida grande", tahoma, verdana, arial, sans-serif; -} -.openerp .oe_form .oe_form_field_many2manytags .text-core .text-wrap .text-dropdown .text-list .text-suggestion em { - font-style: italic; - text-decoration: none; + width: auto; } .openerp .oe_form .oe_datepicker_container { display: none; @@ -1862,72 +2261,55 @@ right: 5px; top: 3px; } -.openerp .oe_form .oe_form_group_cell .oe_kanban_view { - position: static; -} -.openerp .oe_form .oe_form_field_with_button input { - width: 100%; +.openerp .oe_form .oe_form_field_with_button.oe_no_button > .oe_button { + display: none; } -.openerp .oe_form .oe_form_field_with_button > .oe_button { +.openerp .oe_form .oe_form_field_with_button:not(.oe_no_button) > .oe_button { float: right; - -webkit-border-top-left-radius: 0px; - -webkit-border-bottom-left-radius: 0px; - -moz-border-radius-topleft: 0px; - -moz-border-radius-bottomleft: 0px; - border-top-left-radius: 0px; + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; border-bottom-left-radius: 0px; height: 22px; } +.openerp .oe_form .oe_form_field_with_button input { + width: 100%; +} .openerp .oe_form .oe_form_field_with_button > div { position: relative; overflow: hidden; } -.openerp .oe_form .oe_form_title { - width: 55%; -} -.openerp .oe_form .oe_form_title:after { - content: "."; - display: block; - height: 0; - clear: both; - visibility: hidden; -} -.openerp .oe_form .oe_form_button_box { - width: 200px; - text-align: center; +.openerp .oe_form .oe_form_field_html { + position: relative; + width: 600px; + margin-left: 130px; + margin-top: 32px; + margin-bottom: 32px; + text-align: justify; } -.openerp .oe_form .oe_form_group { - margin: 5px 0px 5px 0px; +.openerp .oe_form_editable .oe_form .oe_form_field_integer { + width: 6em !important; } -.openerp .oe_form .oe_form_group_cell .oe_kanban_view { - position: static; +.openerp .oe_form_editable .oe_form .oe_form_field_float { + width: 7em !important; } -.openerp .oe_form .oe_form_button.oe_button { - height: 22px; - white-space: nowrap; +.openerp .oe_form_editable .oe_form .oe_form_field_date { + width: 7.5em !important; } -.openerp .oe_form .oe_form_button.oe_button span { - position: relative; - vertical-align: top; +.openerp .oe_form_editable .oe_form .oe_form_field_datetime { + width: 11.5em !important; } -.openerp .oe_form .oe_form_button > img { - vertical-align: -3px; - padding: 0 2px; -} -.openerp .oe_form .oe-binary-file-set { - overflow: hidden; +.openerp .oe_hidden_input_file { position: relative; - display: inline-block; - width: 45px; - height: 30px; } -.openerp .oe_form input.oe-binary-file { +.openerp .oe_hidden_input_file input.oe_form_binary_file { z-index: 0; line-height: 0; font-size: 12px; position: absolute; top: 1px; - right: 10px; + left: 0; + right: 0; opacity: 0; filter: alpha(opacity=0); -ms-filter: "alpha(opacity=0)"; @@ -1941,210 +2323,344 @@ width: auto; vertical-align: top; } -.openerp .oe_form .oe_form_field_image > img { - min-width: 100px; -} .openerp .oe_form .oe_form_field_image .oe_form_field_image_controls { position: absolute; top: 1px; - padding: 3px 0 0 0; - margin: 0 1px; - filter: alpha(opacity=70); - opacity: 0.7; - width: 99%; + padding: 4px; + width: 100%; + display: none; text-align: center; - background: url(/web/static/src/img/form_sheetbg.png); - border-bottom: 1px dotted black; - -moz-box-sizing: border-box; + color: #eeeeee; + background: rgba(37, 37, 37, 0.9); + -moz-border-radius: 3px 3px 0 0; + -webkit-border-radius: 3px 3px 0 0; + border-radius: 3px 3px 0 0; -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; -ms-box-sizing: border-box; box-sizing: border-box; } .openerp .oe_form .oe_form_field_image:hover .oe_form_field_image_controls { - filter: alpha(opacity=100); - opacity: 1; + display: block; } -.openerp .oe_horizontal_border { - border-bottom: 1px solid black; +.openerp .oe_form_field_many2one td:first-child { + position: relative; } -.openerp .oe_horizontal_separator { - font-weight: bold; - font-size: 20px; - margin: 20px 0px 10px 0px; - color: #aaaabb; +.openerp .oe_form_field_many2one span.oe_m2o_drop_down_button { + position: absolute; + top: 2px; + right: 0px; } -.openerp .oe_horizontal_separator:empty { - height: 5px; +.openerp .oe_form_field_many2one .oe_m2o_cm_button { + line-height: 14px; + float: right; + padding-left: 2px; } -.openerp .oe_vertical_separator { - border-left: 1px solid #666666; - padding: 0 4px 0 4px; +.openerp.ui-autocomplete li.oe_m2o_dropdown_option a { + font-style: italic; + padding-left: 2em; } -.openerp .oe_form_notebook { - margin: 8px 0; - padding: 0 8px; - list-style: none; - zoom: 1; +.openerp.ui-autocomplete li:not(.oe_m2o_dropdown_option) + li.oe_m2o_dropdown_option { + margin-top: 10px; } -.openerp .oe_form_notebook:before, .openerp .oe_form_notebook:after { - display: table; - content: ""; - zoom: 1; +.openerp ul.oe_form_status, .openerp ul.oe_form_status_clickable { + display: inline-block; + margin: 0; + padding: 0 18px 0 0; } -.openerp .oe_form_notebook:after { - clear: both; +.openerp ul.oe_form_status li, .openerp ul.oe_form_status_clickable li { + display: inline-block; + list-style-type: none; + margin: 0 -18px 0 0; + padding: 0; + background-color: #fcfcfc; + background-image: -webkit-gradient(linear, left top, left bottom, from(#fcfcfc), to(#dedede)); + background-image: -webkit-linear-gradient(top, #fcfcfc, #dedede); + background-image: -moz-linear-gradient(top, #fcfcfc, #dedede); + background-image: -ms-linear-gradient(top, #fcfcfc, #dedede); + background-image: -o-linear-gradient(top, #fcfcfc, #dedede); + background-image: linear-gradient(to bottom, #fcfcfc, #dedede); } -.openerp .oe_form_notebook > li { - float: left; +.openerp ul.oe_form_status li:first-child .label, .openerp ul.oe_form_status_clickable li:first-child .label { + border-left: 1px solid #cacaca; + padding-left: 14px; } -.openerp .oe_form_notebook > li > a { - display: block; - color: #4c4c4c; +.openerp ul.oe_form_status li:last-child, .openerp ul.oe_form_status_clickable li:last-child { + border-right: 1px solid #cacaca; } -.openerp .oe_form_notebook { - border-color: #dddddd; - border-style: solid; - border-width: 0 0 1px; +.openerp ul.oe_form_status li:last-child .label, .openerp ul.oe_form_status_clickable li:last-child .label { + padding-right: 14px; } -.openerp .oe_form_notebook > li { +.openerp ul.oe_form_status li:last-child .arrow, .openerp ul.oe_form_status_clickable li:last-child .arrow { + display: none; +} +.openerp ul.oe_form_status li .label, .openerp ul.oe_form_status_clickable li .label { + color: #4c4c4c; + text-shadow: 0 1px 1px #fcfcfc, 0 -1px 1px #dedede; + padding: 7px; + display: inline-block; + padding-left: 24px; + margin: 0; position: relative; - margin-bottom: -1px; } -.openerp .oe_form_notebook > li > a { - padding: 0 12px; - margin-right: 2px; - line-height: 30px; - border: 1px solid transparent; - -moz-border-radius: 4px 4px 0 0; - -webkit-border-radius: 4px 4px 0 0; - border-radius: 4px 4px 0 0; +.openerp ul.oe_form_status li .arrow, .openerp ul.oe_form_status_clickable li .arrow { + width: 17px; + display: inline-block; + vertical-align: top; + overflow: hidden; + margin-left: -5px; } -.openerp .oe_form_notebook > li > a:hover { - text-decoration: none; - background-color: #eeeeee; - border-color: #eeeeee #eeeeee #dddddd; +.openerp ul.oe_form_status li .arrow span, .openerp ul.oe_form_status_clickable li .arrow span { + position: relative; + width: 24px; + height: 24px; + display: inline-block; + margin-left: -12px; + margin-top: 3px; + box-shadow: -1px 1px 2px rgba(255, 255, 255, 0.2), inset -1px 1px 1px rgba(0, 0, 0, 0.2); + background-color: #dedede; + background: -moz-linear-gradient(135deg, #dedede, #fcfcfc); + background: -o-linear-gradient(135deg, #fcfcfc, #dedede); + background: -webkit-gradient(linear, left top, right bottom, from(#fcfcfc), to(#dedede)); + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + -o-transform: rotate(45deg); + transform: rotate(45deg); +} +.openerp ul.oe_form_status li.oe_active, .openerp ul.oe_form_status_clickable li.oe_active { + background-color: #729fcf; + background-image: -webkit-gradient(linear, left top, left bottom, from(#729fcf), to(#3465a4)); + background-image: -webkit-linear-gradient(top, #729fcf, #3465a4); + background-image: -moz-linear-gradient(top, #729fcf, #3465a4); + background-image: -ms-linear-gradient(top, #729fcf, #3465a4); + background-image: -o-linear-gradient(top, #729fcf, #3465a4); + background-image: linear-gradient(to bottom, #729fcf, #3465a4); +} +.openerp ul.oe_form_status li.oe_active .arrow span, .openerp ul.oe_form_status_clickable li.oe_active .arrow span { + background-color: #3465a4; + background: -moz-linear-gradient(135deg, #3465a4, #729fcf); + background: -o-linear-gradient(135deg, #729fcf, #3465a4); + background: -webkit-gradient(linear, left top, right bottom, from(#729fcf), to(#3465a4)); +} +.openerp ul.oe_form_status li.oe_active .label, .openerp ul.oe_form_status_clickable li.oe_active .label { + color: white; + text-shadow: 0 1px 1px #729fcf, 0 -1px 1px #3465a4; } -.openerp .oe_form_notebook > li.ui-state-active > a, .openerp .oe_form_notebook > li.ui-state-active > a:hover { - background-color: white; - border: 1px solid #dddddd; - border-bottom-color: transparent; - cursor: default; +.openerp ul.oe_form_status_clickable li { + cursor: pointer; } -.openerp .oe_form_notebook_page { - padding: 0; +.openerp ul.oe_form_status_clickable li:hover { + background-color: #e8e8e8; + background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#cacaca)); + background-image: -webkit-linear-gradient(top, #e8e8e8, #cacaca); + background-image: -moz-linear-gradient(top, #e8e8e8, #cacaca); + background-image: -ms-linear-gradient(top, #e8e8e8, #cacaca); + background-image: -o-linear-gradient(top, #e8e8e8, #cacaca); + background-image: linear-gradient(to bottom, #e8e8e8, #cacaca); +} +.openerp ul.oe_form_status_clickable li:hover .label { + text-shadow: 0 -1px 1px #fcfcfc, 0 1px 1px #dedede; +} +.openerp ul.oe_form_status_clickable li:hover .arrow span { + background-color: #e8e8e8; + background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#cacaca)); + background-image: -webkit-linear-gradient(top, #e8e8e8, #cacaca); + background-image: -moz-linear-gradient(top, #e8e8e8, #cacaca); + background-image: -ms-linear-gradient(top, #e8e8e8, #cacaca); + background-image: -o-linear-gradient(top, #e8e8e8, #cacaca); + background-image: linear-gradient(to bottom, #e8e8e8, #cacaca); +} +.openerp ul.oe_form_status_clickable li .label { + color: #7c7bad; +} +.openerp ul.oe_form_status_clickable li.oe_active:hover { + background-color: #4c85c2; + background-image: -webkit-gradient(linear, left top, left bottom, from(#4c85c2), to(#284d7d)); + background-image: -webkit-linear-gradient(top, #4c85c2, #284d7d); + background-image: -moz-linear-gradient(top, #4c85c2, #284d7d); + background-image: -ms-linear-gradient(top, #4c85c2, #284d7d); + background-image: -o-linear-gradient(top, #4c85c2, #284d7d); + background-image: linear-gradient(to bottom, #4c85c2, #284d7d); +} +.openerp ul.oe_form_status_clickable li.oe_active:hover .label { + text-shadow: 0 -1px 1px #729fcf, 0 1px 1px #3465a4; +} +.openerp ul.oe_form_status_clickable li.oe_active:hover .arrow span { + background-color: #284d7d; + background: -moz-linear-gradient(135deg, #284d7d, #4c85c2); + background: -o-linear-gradient(135deg, #4c85c2, #284d7d); + background: -webkit-gradient(linear, left top, right bottom, from(#4c85c2), to(#284d7d)); +} +.openerp .oe_form .oe_form_field_one2many > .oe_view_manager .oe_list_pager_single_page { + display: none; } -.openerp div.ui-tabs { - padding: 3px 0px 3px 0px; +.openerp .oe_form_field_one2many > .oe_view_manager .oe_list_pager_single_page, .openerp .oe_form_field_many2many > .oe_view_manager .oe_list_pager_single_page { + display: none !important; } -.openerp .ui-tabs-hide { - display: none; +.openerp .oe_form_field_one2many .oe_form_field_one2many_list_row_add, .openerp .oe_form_field_many2many .oe_form_field_one2many_list_row_add { + font-weight: bold; } -.openerp .oe_form .oe_form_field_progressbar { - display: inline-block; - min-width: 70px; +.openerp .oe_form_field_one2many .oe_list_content > thead, .openerp .oe_form_field_many2many .oe_list_content > thead { + border-bottom: 1px; } -.openerp .oe_form .oe_form_field_progressbar.ui-progressbar { - height: 22px; - font-size: 10px; - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - -ms-box-sizing: border-box; - box-sizing: border-box; - border: 1px solid #999999; - -moz-border-radius: 3px; - -webkit-border-radius: 3px; - border-radius: 3px; - background: white; - min-width: 50px; +.openerp .oe_form_field_one2many .oe_list_content > tbody tr:nth-child(odd), .openerp .oe_form_field_many2many .oe_list_content > tbody tr:nth-child(odd) { + background: transparent; } -.openerp .oe_form .oe_form_field_progressbar.ui-progressbar span { - position: absolute; - margin-left: 10px; - font-weight: bold; +.openerp .oe_form_field_one2many .oe_list .oe_list_edit_row_save, .openerp .oe_form_field_many2many .oe_list .oe_list_edit_row_save { + background: url(/web/static/src/img/iconset-b-remove.png) 50% 50% no-repeat; } -.openerp .oe_form .oe_form_field_progressbar.ui-progressbar .ui-widget-header { - background: #cccccc url(/web/static/lib/jquery.ui/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; +.openerp .oe_form_field_one2many .oe_list .oe_list_edit_row_save:before, .openerp .oe_form_field_many2many .oe_list .oe_list_edit_row_save:before { + visibility: hidden; +} +.openerp .oe_form_field_one2many > .oe_view_manager .oe_header_row_top, .openerp .oe_form_field_many2many > .oe_view_manager .oe_header_row_top { + display: none; } -.openerp .oe_form .oe_view_manager_header2 td { +.openerp .oe_form_field_one2many > .oe_view_manager .oe_view_manager_header2 td, .openerp .oe_form_field_many2many > .oe_view_manager .oe_view_manager_header2 td { padding: 0px 8px; line-height: 16px; } -.openerp .oe_form .oe_view_manager_header2 td .oe_i { +.openerp .oe_form_field_one2many > .oe_view_manager .oe_view_manager_header2 td .oe_i, .openerp .oe_form_field_many2many > .oe_view_manager .oe_view_manager_header2 td .oe_i { font-size: 13px; } -.openerp .oe_form .oe_view_manager_header2 td .oe_pager_group { +.openerp .oe_form_field_one2many > .oe_view_manager .oe_view_manager_header2 td .oe_pager_group, .openerp .oe_form_field_many2many > .oe_view_manager .oe_view_manager_header2 td .oe_pager_group { height: auto; line-height: 16px; } -.openerp .oe_form .oe_view_manager_header2 td .oe_pager_group li { +.openerp .oe_form_field_one2many > .oe_view_manager .oe_view_manager_header2 td .oe_pager_group li, .openerp .oe_form_field_many2many > .oe_view_manager .oe_view_manager_header2 td .oe_pager_group li { height: auto; line-height: 16px; } -.openerp .oe-select-create-popup-view-form > .oe_formview > .oe_form_pager { - display: none; +.openerp .oe_form_field_one2many .oe_list_buttons.oe_editing .oe_list_save, .openerp .oe_form_field_many2many .oe_list_buttons.oe_editing .oe_list_save { + visibility: hidden; } -.openerp .oe_form td.oe_form_group_cell_label { - border-right: 1px solid #dddddd; - padding: 2px 0px 2px 0px; +.openerp .oe_form_editable .oe_list_editable .oe_list_content td.oe_required { + background-color: #d2d2ff; } -.openerp .oe_form td.oe_form_group_cell_label label { - line-height: 18px; - display: block; - min-width: 120px; +.openerp .oe_form_editable .oe_list_editable .oe_list_content td.oe_readonly { + background-color: #eeeeee; } -.openerp .oe_form td.oe_form_group_cell + .oe_form_group_cell { - padding-left: 8px; +.openerp .oe_list_editable .oe_list_content td.oe_list_field_cell { + padding: 4px 6px 3px 6px; } -.openerp .oe_form .oe_form_subtotal_footer { - width: auto; - float: right; +.openerp .oe_list.oe_list_editable td.oe_list_record_delete { + position: absolute; +} +.openerp .oe_list.oe_list_editable.oe_editing .oe_m2o_drop_down_button { + top: 5px; +} +.openerp .oe_list.oe_list_editable.oe_editing .oe_m2o_cm_button { + display: none; +} +.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field textarea { + height: 27px; } -.openerp .oe_form .oe_form_subtotal_footer td.oe_form_group_cell { +.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field textarea { + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; + border: 1px solid #aaaaff; + margin: 0; +} +.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_float input, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_view_integer input { text-align: right; - padding: 0; + width: 100%; } -.openerp .oe_form .oe_form_subtotal_footer td.oe_form_group_cell_label { - border: none; +.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_datetime > span, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_date > span { + width: 100%; } -.openerp .oe_form .oe_form_subtotal_footer .oe_form_field { - width: auto !important; +.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_datetime input.oe_datepicker_master, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_date input.oe_datepicker_master { + width: 100% !important; } -.openerp .oe_form .oe_form_subtotal_footer .oe_form_subtotal_footer_separator { - border-top: 1px solid #cacaca; - font-size: 120%; - font-weight: bold; +.openerp .oe_list.oe_list_editable.oe_editing .oe_form_field .oe_form_field_float, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field .oe_form_view_integer, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_datetime, .openerp .oe_list.oe_list_editable.oe_editing .oe_form_field.oe_form_field_date { + min-width: 0 !important; + max-width: none !important; } -.openerp .oe_form .oe_form_subtotal_footer label.oe_form_subtotal_footer_separator { - font-weight: bold !important; - padding: 2px 8px 2px 0px !important; +.openerp .oe_form .oe_form_field_many2many > .oe_list .oe_list_pager_single_page { + display: none; +} +.openerp .oe_list_buttons .oe_alternative { + visibility: hidden; +} +.openerp .oe_list_buttons .oe_list_save, .openerp .oe_list_buttons .oe_list_discard { + display: none; +} +.openerp .oe_list_buttons.oe_editing .oe_list_add { + display: none; +} +.openerp .oe_list_buttons.oe_editing .oe_list_save { + display: inline-block; } -.openerp .oe-listview-content { +.openerp .oe_list_buttons.oe_editing .oe_list_discard { + display: inline; +} +.openerp .oe_list_buttons.oe_editing .oe_alternative { + visibility: visible; +} +.openerp .oe_list { + position: relative; +} +.openerp .oe_list .oe_form .oe_form_nosheet { + margin: 0; + padding: 0; + border: none; +} +.openerp .oe_list .oe_form .oe_form_field { + width: auto; + position: absolute; + margin: 0 !important; + padding: 0; +} +.openerp .oe_list .oe_list_content .oe_group_header { + background-color: #fcfcfc; + background-image: -webkit-gradient(linear, left top, left bottom, from(#fcfcfc), to(#dedede)); + background-image: -webkit-linear-gradient(top, #fcfcfc, #dedede); + background-image: -moz-linear-gradient(top, #fcfcfc, #dedede); + background-image: -ms-linear-gradient(top, #fcfcfc, #dedede); + background-image: -o-linear-gradient(top, #fcfcfc, #dedede); + background-image: linear-gradient(to bottom, #fcfcfc, #dedede); +} +.openerp .oe_list_content { width: 100%; } -.openerp .oe-listview-content > thead { +.openerp .oe_list_content td:first-child:after, .openerp .oe_list_content th:first-child:after { + border-width: 0; +} +.openerp .oe_list_content td.oe_number { + text-align: right !important; + max-width: 100px; +} +.openerp .oe_list_content > thead { border-bottom: 2px solid #cacaca; background: #eeeeee; vertical-align: top; } -.openerp .oe-listview-content > thead td, .openerp .oe-listview-content > thead th { +.openerp .oe_list_content > thead th { + position: relative; +} +.openerp .oe_list_content td, .openerp .oe_list_content th { padding: 3px 6px; line-height: 18px; } -.openerp .oe-listview-content > thead th:after { +.openerp .oe_list_content th:after { + position: absolute; + right: 6px; content: ""; - float: right; margin-top: 7px; border-width: 0 4px 4px; border-style: solid; border-color: black transparent; visibility: hidden; } -.openerp .oe-listview-content > thead th.sortup:after { +.openerp .oe_list_content th.sortup:after { visibility: visible; filter: alpha(opacity=60); opacity: 0.6; } -.openerp .oe-listview-content > thead th.sortdown:after { +.openerp .oe_list_content th.sortdown:after { border-bottom: none; border-left: 4px solid transparent; border-right: 4px solid transparent; @@ -2152,41 +2668,36 @@ visibility: visible; -moz-box-shadow: none; -webkit-box-shadow: none; - -box-shadow: none; + box-shadow: none; filter: alpha(opacity=60); opacity: 0.6; } -.openerp .oe-listview-content > tbody { +.openerp .oe_list_content > tbody { cursor: pointer; - background: white; } -.openerp .oe-listview-content > tbody > tr { +.openerp .oe_list_content > tbody > tr { + height: 27px; border-top: 1px solid #dddddd; } -.openerp .oe-listview-content > tbody > tr > td.oe-field-cell { +.openerp .oe_list_content > tbody > tr > td.oe_list_field_cell { padding: 3px 6px; + white-space: pre-line; } -.openerp .oe-listview-content > tbody > tr > td, .openerp .oe-listview-content > tbody > tr > th { - line-height: 18px; -} -.openerp .oe-listview-content > tbody > tr > td > button, .openerp .oe-listview-content > tbody > tr > th > button { +.openerp .oe_list_content > tbody > tr > td > button, .openerp .oe_list_content > tbody > tr > th > button { border: none; background: transparent; padding: 0; -moz-box-shadow: none; -webkit-box-shadow: none; - -box-shadow: none; + box-shadow: none; } -.openerp .oe-listview-content > tbody > tr > td.oe_list_checkbox:first-child, .openerp .oe-listview-content > tbody > tr th.oe_list_checkbox:first-child { +.openerp .oe_list_content > tbody > tr > td.oe_list_checkbox:first-child, .openerp .oe_list_content > tbody > tr th.oe_list_checkbox:first-child { width: 17px; } -.openerp .oe-listview-content > tbody > tr > td.oe_list_checkbox:first-child:after, .openerp .oe-listview-content > tbody > tr th.oe_list_checkbox:first-child:after { +.openerp .oe_list_content > tbody > tr > td.oe_list_checkbox:first-child:after, .openerp .oe_list_content > tbody > tr th.oe_list_checkbox:first-child:after { border-width: 0; } -.openerp .oe-listview-content > tbody > tr > td.oe_number { - text-align: right !important; -} -.openerp .oe-listview-content > tbody > tr:nth-child(odd) { +.openerp .oe_list_content > tbody > tr:nth-child(odd) { background-color: #f0f0fa; background-color: #f0f0fa; background-image: -webkit-gradient(linear, left top, left bottom, from(#f0f0fa), to(#eeeef6)); @@ -2196,51 +2707,105 @@ background-image: -o-linear-gradient(top, #f0f0fa, #eeeef6); background-image: linear-gradient(to bottom, #f0f0fa, #eeeef6); } -.openerp .oe-listview-content > tfoot { +.openerp .oe_list_content > tfoot { border-top: 2px solid #cacaca; border-bottom: 1px solid #cacaca; background: #eeeeee; font-weight: bold; } -.openerp .oe-listview-content > tbody tr:hover td, .openerp .oe-listview-content tbody tr:hover th { - background-color: #eeeeee; - background-image: -webkit-gradient(linear, left top, left bottom, from(#eeeeee), to(#dedede)); - background-image: -webkit-linear-gradient(top, #eeeeee, #dedede); - background-image: -moz-linear-gradient(top, #eeeeee, #dedede); - background-image: -ms-linear-gradient(top, #eeeeee, #dedede); - background-image: -o-linear-gradient(top, #eeeeee, #dedede); - background-image: linear-gradient(to bottom, #eeeeee, #dedede); -} -.openerp .oe-listview-content .numeric { +.openerp .oe_list_content .numeric { text-align: right; width: 82px; } -.openerp .oe-listview-content .numeric input { +.openerp .oe_list_content .numeric input { text-align: right; } -.openerp .oe_trad_field.touched { - border: 1px solid green !important; +.openerp .oe_list_content th.oe_list_header_handle { + font-size: 1px; + overflow: hidden; + text-indent: -9001px; } -.openerp.oe_tooltip { - font-size: 12px; +.openerp .oe_list_content td.oe_list_field_handle { + width: 1em; + padding: 0 !important; + cursor: ns-resize; } -.openerp.oe_tooltip .oe_tooltip_string { - color: #ffdd55; - font-weight: bold; - font-size: 13px; +.openerp .oe_list_content td.oe_list_field_handle .oe_list_handle { + font-size: 1px; + letter-spacing: -1px; + color: transparent; + font-weight: normal; + margin-right: 7px; } -.openerp.oe_tooltip .oe_tooltip_help { - white-space: pre-wrap; +.openerp .oe_list_content td.oe_list_field_handle .oe_list_handle:before { + font: 18px "entypoRegular"; + content: "}"; + color: #e0e0e0; } -.openerp.oe_tooltip .oe_tooltip_technical { - padding: 0 0 4px 0; - margin: 5px 0 0 15px; +.openerp .oe_list_cannot_edit .oe_list_header_handle, .openerp .oe_list_cannot_edit .oe_list_field_handle { + display: none !important; + padding: 0 !important; } -.openerp.oe_tooltip .oe_tooltip_technical li { - list-style: circle; +.openerp .oe_list_cannot_delete .oe_list_record_delete { + display: none !important; } -.openerp.oe_tooltip .oe_tooltip_technical_title { +.openerp .tree_header { + background-color: #f0f0f0; + border-bottom: 1px solid #cacaca; + color: #4c4c4c; + padding: 5px; + height: 25px; +} +.openerp .tree_header button { + float: right; + height: 27px; + margin-right: 5px; +} +.openerp .oe-treeview-table { + width: 100%; + background-color: white; + border-spacing: 0; + color: #4c4c4c; +} +.openerp .oe-treeview-table th { + padding: 10px; font-weight: bold; + background-color: #f0f0f0; + border-bottom: 2px solid #cacaca; +} +.openerp .oe-treeview-table td { + cursor: pointer; + vertical-align: middle; + text-align: left; + vertical-align: middle; + height: 20px; + padding-left: 4px; + padding-right: 4px; + border-right: 1px solid #e7e7e7; +} +.openerp .oe-treeview-table td.oe_number { + text-align: right !important; +} +.openerp .oe-treeview-table tr { + border-bottom: 1px solid #d6d6d6; +} +.openerp .oe-treeview-table tr:hover { + background-color: #e7e7e7; +} +.openerp .oe-treeview-table span { + font-size: 90%; + font-weight: normal; + white-space: nowrap; + display: block; +} +.openerp .oe-treeview-table .treeview-tr.oe-treeview-first { + background: transparent url(/web/static/src/img/expand.gif) 0 50% no-repeat; +} +.openerp .oe-treeview-table .oe_open .treeview-tr.oe-treeview-first { + background-image: url(/web/static/src/img/collapse.gif); +} +.openerp .oe-treeview-table .treeview-tr.oe-treeview-first span, .openerp .oe-treeview-table .treeview-td.oe-treeview-first span { + margin-left: 16px; } .openerp .oe_layout_debugging .oe_form_group { outline: 2px dashed green; @@ -2283,28 +2848,6 @@ color: #333333; } -.openerp .oe_view_editor { - width: 100%; - border-collapse: collapse; - margin-left: -12px; - width: 100%; - background-color: white; - border-spacing: 0; -} -.openerp .oe_view_editor td { - text-align: center; - white-space: nowrap; - border: 1px solid #d8d8d8; - cursor: pointer; - font-size: 90%; -} -.openerp .oe_view_editor_field td { - border: 0px !important; -} -.openerp .oe_view_editor tr:hover { - background-color: #ecebf2; -} - .kitten-mode-activated { background-image: url(http://placekitten.com/g/1365/769); background-size: cover; @@ -2314,11 +2857,47 @@ opacity: 0.7; } -.openerp .oe_form_field_many2one td:first-child { - position: relative; +div.ui-widget-overlay { + background: black; + filter: alpha(opacity=30); + opacity: 0.3; } -.openerp .oe_form_field_many2one span.oe-m2o-drop-down-button { - position: absolute; - top: 3px; - right: 2px; + +.ui-widget { + font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif; + color: #4c4c4c; + font-size: 13px; +} + +.ui-menu { + padding: 2px 0; + -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3); + margin-top: 4px; + border: 1px solid #afafb6; +} +.ui-menu .ui-menu-item { + width: 100%; + padding: 0; +} +.ui-menu .ui-menu-item a { + padding: 1px 16px; +} +.ui-menu .ui-menu-item a.ui-corner-all { + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; +} +.ui-menu .ui-menu-item a.ui-state-active { + background: #f0f0fa; +} +.ui-menu .ui-menu-item a.ui-state-hover, .ui-menu .ui-menu-item a.ui-state-active { + background: #7c7bad; +} + +.ui-corner-all { + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; } diff --git a/addons/web/static/src/css/base.sass b/addons/web/static/src/css/base.sass index c4d9d565b8b..851d0c3f9c9 100644 --- a/addons/web/static/src/css/base.sass +++ b/addons/web/static/src/css/base.sass @@ -1,11 +1,15 @@ @charset "utf-8" // Variables {{{ -$section-title-color: #8786b7 $facets-border: #afafb6 -$facets-border-selected: #a6a6fe +$section-title-color: #7C7BAD +$tag-bg-light: #f0f0fa +$tag-bg-dark: #7C7BAD +$tag-border: #afafb6 +$tag-border-selected: #a6a6fe $hover-background: #f0f0fa -$colour4: #8a89ba +$link-color: #7C7BAD +$sheet-max-width: 860px // }}} // Mixins {{{ @font-face @@ -27,6 +31,14 @@ $colour4: #8a89ba font-weight: normal font-style: normal +@mixin reset() + border: none + padding: 0 + margin: 0 + background: none + @include radius(0) + @include box-shadow(none) + @mixin vertical-gradient($startColor: #555, $endColor: #333) background-color: $startColor background-image: -webkit-gradient(linear, left top, left bottom, from($startColor), to($endColor)) /* Saf4+, Chrome */ @@ -36,7 +48,6 @@ $colour4: #8a89ba background-image: -o-linear-gradient(top, $startColor, $endColor) /* Opera 11.10+ */ background-image: linear-gradient(to bottom, $startColor, $endColor) - @mixin radial-gradient($gradient) background-position: center center background-image: -webkit-radial-gradient(circle, $gradient) @@ -52,7 +63,7 @@ $colour4: #8a89ba @mixin box-shadow($bsval: 0px 1px 4px #777) -moz-box-shadow: $bsval -webkit-box-shadow: $bsval - -box-shadow: $bsval + box-shadow: $bsval @mixin transition($transval: (border linear 0.2s, box-shadow linear 0.2s)) -webkit-transition: $transval @@ -70,10 +81,38 @@ $colour4: #8a89ba -moz-background-clip: $clip background-clip: $clip -@mixin unscrew-box - -moz-box-sizing: border-box - -webkit-box-sizing: border-box - box-sizing: border-box +@mixin box-sizing($type: content) + // type = border || content || padding + -webkit-box-sizing: #{$type}-box + -moz-box-sizing: #{$type}-box + -ms-box-sizing: #{$type}-box + box-sizing: #{$type}-box + +@mixin skew-gradient($startColor: #555, $endColor: #333) + background-color: $endColor + background: -moz-linear-gradient(135deg, $endColor, $startColor) + background: -o-linear-gradient(135deg, $startColor, $endColor) + background: -webkit-gradient(linear, left top, right bottom, from($startColor), to($endColor)) + +@mixin transform($transform) + -webkit-transform: $transform + -moz-transform: $transform + -ms-transform: $transform + -o-transform: $transform + transform: $transform + +@mixin keyframes($name) + @-webkit-keyframes #{$name} + @content + @-moz-keyframes #{$name} + @content + @-ms-keyframes #{$name} + @content + @-o-keyframes #{$name} + @content + @keyframes #{$name} + @content + // Transforms the (readable) text of an inline element into an mmlicons icon, // allows for actual readable text in-code (and in readers?) with iconic looks @@ -81,18 +120,37 @@ $colour4: #8a89ba font-size: 1px letter-spacing: -1px color: transparent + font-weight: normal &:before - font-family: "mnmliconsRegular" + font: 21px "mnmliconsRegular" + content: $icon-name + color: $color + +@mixin text-to-entypo-icon($icon-name, $color: #404040, $size: 21px) + font-size: 1px + letter-spacing: -1px + color: transparent + font-weight: normal + &:before + font: $size "entypoRegular" content: $icon-name - font-size: 20px color: $color // }}} -.openerp.openerp-web-client-container +@media print + .oe_topbar, .oe_leftbar, .oe_loading + display: none !important + +.openerp.openerp_webclient_container height: 100% position: relative +// jQueryUI css bug fixing +.text-tag .text-button + height: auto !important + min-height: 16px + .openerp // Global style {{{ padding: 0 @@ -101,19 +159,28 @@ $colour4: #8a89ba color: #4c4c4c font-size: 13px background: white + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5) // }}} + //Placeholder style{{{ + \:-moz-placeholder + color: $tag-border !important + font-style: italic !important + \::-webkit-input-placeholder + color: $tag-border !important + font-style: italic !important + //}}} // Tag reset {{{ a text-decoration: none + cursor: pointer !important table padding: 0 - font-size: 13px border-collapse: collapse thead font-weight: bold background-color: #f0f0f0 th - border-right: 1px dotted #afafb6 + border-right: 1px dotted $tag-border &:last-child border-right: none th, td @@ -129,53 +196,21 @@ $colour4: #8a89ba @include vertical-gradient(#f0f0fa, #eeeef6) .zebra tbody tr:hover td @include vertical-gradient(#eee, #dedede) - .numeric - text-align: right - width: 82px - input - text-align: right - ul, li, ol - margin: 0 - padding: 0 - li - list-style-type: none - input, textarea + input, textarea, select + padding: 2px 4px border: 1px solid #ccc @include radius(3px) - - // }}} - // Generic classes {{{ - .oe_i - font-family: "mnmliconsRegular" !important - font-size: 21px - font-weight: 300 !important - .oe_e - font-family: "entypoRegular" !important - font-size: 34px - font-weight: 300 !important - .oe_left - float: left - margin-right: 8px - .oe_right - float: right - margin-left: 8px - .oe_text_right - text-align: right - .oe_clear - clear: both - .oe_wait - cursor: wait - .oe_fade - color: #888 - //font-size: 12px - .oe_bold - font-weight: bold + background: white + img + vertical-align: middle + h4 + margin: 4px 0 // }}} // Button style {{{ - a.button:link, a.button:visited, button, input[type='submit'] + a.button:link, a.button:visited, button, input[type='submit'], .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button display: inline-block - border: 1px solid #ababab - color: #404040 + border: 1px solid rgba(0,0,0,0.4) + color: #4c4c4c margin: 0 padding: 3px 12px font-size: 13px @@ -187,20 +222,19 @@ $colour4: #8a89ba -webkit-font-smoothing: antialiased outline: none - a.button:hover, button:hover, input[type='submit']:hover + a.button:hover, button:hover, input[type='submit']:hover, .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-hover @include vertical-gradient(#f6f6f6, #e3e3e3) cursor: pointer + background-position: 0 - a.button:focus, button:focus, input[type='submit']:focus + a.button:focus, button:focus, input[type='submit']:focus, .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-focus border: 1px solid #80bfff + background-position: 0 @include vertical-gradient(#f6f6f6, #e3e3e3) @include box-shadow((0 0 3px #80bfff, 0 1px 1px rgba(255, 255, 255, .8) inset)) - a.button:active, a.button.active, button:active, button.active, input[type='submit']:active, input[type='submit'].active - background: #e3e3e3 - background: -moz-linear-gradient(top, #e3e3e3, #f6f6f6) #1b468f - background: -webkit-gradient(linear, left top, left bottom, from(#e3e3e3), to(#f6f6f6)) #1b468f - background: linear-gradient(top, #e3e3e3, #f6f6f6) #1b468f + a.button:active, a.button.active, button:active, button.active, input[type='submit']:active, input[type='submit'].active, .ui-dialog-buttonpane .ui-dialog-buttonset .ui-button.ui-state-active + @include vertical-gradient(#e3e3e3, #f6f6f6) @include box-shadow(none) a.button.disabled, button:disabled, input[type='submit']:disabled @@ -210,31 +244,405 @@ $colour4: #8a89ba color: #aaa !important cursor: default text-shadow: 0 1px 1px #fff !important + // }}} + // Jquery ui Overrides {{{ + .ui-widget-content a + color: $link-color + .oe_bounce_container + display: inline-block - button.primary - color: white - @include vertical-gradient(#dc5f59, #b33630) - @include box-shadow(none) + // Modal box + &.ui-dialog + display: none + padding: 6px + //overflow: hidden + background-color: rgba(60,60,60,0.7) + border: 1px solid + border-color: #888 #555 #444 + //overflow: hidden + @include radius(8px) + @include box-shadow(0 1px 12px rgba(0, 0, 0, 0.6)) + @include background-clip() + .ui-dialog-content + padding: 0px + .ui-dialog-titlebar, .ui-dialog-content, .ui-dialog-buttonpane + padding: 16px + .ui-dialog-titlebar + border-bottom: 1px solid #cacaca + @include radius(2px 2px 0 0) + @include vertical-gradient(#FCFCFC, #DEDEDE) + .ui-dialog-title + margin: 0 + padding: 0 + .ui-widget-header + border: none + .ui-dialog-content + background: white + .ui-dialog-buttonpane + border-top: 1px solid #e0e0e0 + background: #f5f7f9 + margin: 0 + @include radius(0 0 2px 2px) + button + margin: 0 4px 0 0 + .ui-dialog-buttonset + float: left + .ui-button + margin-right: 4px + .ui-dialog-titlebar-close + padding: 0 + .ui-icon-closethick + display: none + &:before + content: "×" + font-size: 18px + font-weight: bold + line-height: 16px + color: black + text-shadow: 0 1px 0 white + padding: 0 + cursor: pointer + background: transparent + border: 0 + &:hover + color: black + text-decoration: none + .oe_about + background-color: white + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAKUlEQVQIHWO8e/fufwYsgAUkJigoiCIF5DMyoYggcUiXgNnBiGQKmAkARpcEQeriln4AAAAASUVORK5CYII=) + @include radius(0 0 2px 2px) + a + color: $link-color + &:hover + text-decoration: underline + &:focus + outline: none + .oe_logo + margin-left: -6px + .oe_bottom + position: absolute + top: 50% + left: 0 + right: 0 + bottom: 0 + text-shadow: 0 1px 1px #999999 + @include vertical-gradient(#b41616, #600606) + color: #eee + padding: 0 16px + @include radius(0 0 2px 2px) + a + color: #eee - button.primary:hover - @include vertical-gradient(lighten(#dc5f59, 3%), lighten(#b33630, 3%)) - @include box-shadow(0 0 1px rgba(0,0,0,0.2)) + &.ui-dialog.oe_act_window + .ui-dialog-content + padding: 0px - button.primary:active - @include vertical-gradient(#b33630, #dc5f59) - @include box-shadow(none) + .modal-backdrop + position: fixed + top: 0 + right: 0 + bottom: 0 + left: 0 + z-index: 1040 + background-color: black + @include opacity(.3) - button.following + // }}} + // Generic classes {{{ + .oe_i + font-family: "mnmliconsRegular" !important + font-size: 21px + font-weight: 300 !important + .oe_e + font-family: "entypoRegular" !important + font-size: 34px + font-weight: 300 !important + .oe_left + float: left + margin-right: 8px + .oe_right + float: right + margin-left: 8px + .oe_text_right + text-align: right + .oe_clear + clear: both + .oe_wait + cursor: wait + .oe_fade + color: #888 + font-weight: normal + .oe_bold + font-weight: bold + .oe_inline + width: auto !important + .oe_highlight color: white - width: 120px - @include vertical-gradient($colour4, darken($colour4, 3%)) + background: #DC5F59 + button.oe_highlight + @include vertical-gradient(#df3f3f, #a21a1a) + @include box-shadow((0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(155, 155, 155, 0.4) inset)) + button.oe_highlight:hover + @include vertical-gradient(lighten(#e04f4f, 2%), lighten(#a21a1a, 2%)) + button.oe_highlight:active + @include vertical-gradient(lighten(#a21a1a, 8%), darken(#df3f3f, 8%)) + .oe_background_grey + background: #eee !important - button.unfollow - display: none - color: white - width: 120px - @include vertical-gradient(#dc5f59, #b33630) + .oe_form_dirty + .oe_highlight_on_dirty + color: white + background: #dc5f59 + font-weight: bold + button.oe_highlight_on_dirty + @include vertical-gradient(#dc5f59, #b33630) + @include box-shadow(none) + &:hover + background: #ED6F6A + .oe_title + width: 50% + float: left + .oe_title:after + content: "." + display: block + height: 0 + clear: both + visibility: hidden + .oe_button_box + width: 270px + text-align: right + button + margin: 4px + .oe_avatar + > img + max-height: 90px + max-width: 90px + @include radius(3px) + @include box-shadow(0 1px 4px rgba(0, 0, 0, 0.4)) + border: none + margin-bottom: 10px + .oe_avatar + div + margin-left: 5px + .oe_image_small > img + max-width: 50px + max-height: 50px + .oe_image_medium > img + max-width: 180px + max-height: 180px + .oe_button.oe_link + @include reset() + img + display: none + span + @include reset() + color: $link-color + font-weight: bold + &:hover + text-decoration: underline + .oe_webclient + .oe_star_on, .oe_star_off + color: #cccccc + text-shadow: 0 0 2px black + vertical-align: top + position: relative + top: -5px + .oe_star_on:hover, .oe_star_off:hover + text-decoration: none + .oe_star_on + color: gold + // }}} + // Tags (for many2many tags, among others) {{{ + .oe_tag + border: 1px solid $tag-border + font-size: 11px + padding: 2px 4px + margin: 0 2px 2px 0 + @include radius(3px) + background: $tag-bg-light + color: #4C4C4C + .oe_tag_dark + background: $tag-bg-dark + color: #eee + .oe_tags + &.oe_inline + min-width: 250px + .text-wrap + width: 100% !important + textarea + width: 100% !important + .text-core .text-wrap .text-dropdown .text-list .text-suggestion em + font-style: italic + text-decoration: none + margin-bottom: 1px + // }}} + // Tooltips {{{ + &.oe_tooltip + font-size: 12px + .oe_tooltip_string + color: #FD5 + font-weight: bold + font-size: 13px + .oe_tooltip_help + white-space: pre-wrap + .oe_tooltip_technical + padding: 0 0 4px 0 + margin: 5px 0 0 15px + li + list-style: circle + .oe_tooltip_technical_title + font-weight: bold + .oe_tooltip_close + margin: -5px 0 0 2px + cursor: default + float: right + color: white + &:hover + color: #999 + cursor: pointer + .oe_tooltip_message + max-width: 310px + // }}} + // Notebook {{{ + .oe_notebook + margin: 8px 0 + padding: 0 16px + list-style: none + zoom: 1 + .oe_notebook.ui-corner-all + @include radius(0) + .oe_notebook:before, .oe_notebook:after + display: table + content: "" + zoom: 1 + .oe_notebook:after + clear: both + .oe_notebook > li + float: left + .oe_notebook > li > a + display: block + color: #4c4c4c + .oe_notebook + border-color: #ddd + border-style: solid + border-width: 0 0 1px + .oe_notebook > li + position: relative + .oe_notebook > li > a + padding: 0 12px + margin-right: 2px + line-height: 30px + border: 1px solid transparent + @include radius(4px 4px 0 0) + .oe_notebook > li > a:hover + text-decoration: none + background-color: #eee + border-color: #eee #eee #ddd + .oe_notebook > li.ui-state-active > a, .oe_notebook > li.ui-state-active > a:hover + background-color: #ffffff + border: 1px solid #ddd + border-bottom-color: transparent + cursor: default + .oe_notebook_page + padding: 0 + div.ui-tabs + padding: 3px 0px 3px 0px + .ui-tabs-hide + display: none + // }}} + // Dropdown {{{ + .oe_dropdown, .oe_dropdown_hover, .oe_dropdown_toggle + position: relative + cursor: pointer + .oe_dropdown_toggle + color: rgba(0,0,0,0.5) + font-weight: normal + .oe_dropdown_hover:hover .oe_dropdown_menu, .oe_dropdown_menu.oe_opened + display: block + .oe_dropdown_menu + display: none + position: absolute + top: 26px + left: 0 + z-index: 3 + margin: 0 + padding: 0 + border: 1px solid $tag-border + background: white + padding: 4px 0 + min-width: 140px + text-align: left + @include radius(3px) + @include box-shadow(0 1px 4px rgba(0,0,0,0.3)) + > li + &:hover + @include vertical-gradient(#f0f0fa, #eeeef6) + @include box-shadow(none) + list-style-type: none + float: none + display: block + position: relative + margin: 0 + padding: 2px 8px + > a + white-space: nowrap + display: block + color: #4c4c4c + text-decoration: none + &:hover + text-decoration: none + .oe_dropdown_arrow:after + width: 0 + height: 0 + display: inline-block + content: "&darr" + text-indent: -99999px + vertical-align: top + margin-top: 8px + //margin-left set at 3px to avoid a strange overflow + margin-left: 3px + border-left: 4px solid transparent + border-right: 4px solid transparent + border-top: 4px solid#404040 + @include opacity(0.5) + .oe_sidebar + white-space: nowrap + .oe_dropdown_menu + .oe_sidebar_add_attachment + height: 20px + cursor: pointer + padding-left: 6px + margin-top: 6px + span + font-weight: bold + .oe_hidden_input_file + width: 200px + &:hover + @include vertical-gradient(#f0f0fa, #eeeef6) + @include box-shadow(none) + li + .oe_sidebar_delete_item + position: absolute + top: 4px + right: 4px + display: none + width: 12px + height: 12px + padding: 1px + color: #8786b7 + line-height: 8px + text-align: center + font-weight: bold + text-shadow: 0 1px 1px white + &:hover + text-decoration: none + color: white + background: #8786b7 + text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4) + @include radius(2px) + &:hover + .oe_sidebar_delete_item + display: inline-block // }}} // Loading {{{ .oe_loading @@ -260,13 +668,14 @@ $colour4: #8a89ba // }}} // Login {{{ .oe_login - background: url("/web/static/src/img/pattern.png") repeat + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAKUlEQVQIHWO8e/fufwYsgAUkJigoiCIF5DMyoYggcUiXgNnBiGQKmAkARpcEQeriln4AAAAASUVORK5CYII=) text-align: center font-size: 14px height: 100% - li + ul, li + margin: 0 list-style-type: none - padding-bottom: 4px + padding: 0 0 4px 0 button float: right display: inline-block @@ -283,7 +692,7 @@ $colour4: #8a89ba font-family: "Lucida Grande", Helvetica, Verdana, Arial border: 1px solid #999999 background: whitesmoke - @include box-shadow(inset 0 1px 4px rgba(0, 0, 0, 0.3)) + @include box-shadow(inset 0 1px 4px rgba(0, 0, 0, 0.4)) @include radius(3px) input margin-bottom: 9px @@ -383,64 +792,119 @@ $colour4: #8a89ba .oe_topbar, .oe_leftbar display: none // }}} - // WebClient.topbar {{{ + // Top Menu {{{ + .oe_topbar width: 100% - height: 31px - border-top: solid 1px #d3d3d3 + height: 32px + background-color: #414141 @include vertical-gradient(#646060, #262626) - .oe_topbar_item - li - float: left - a - display: block - padding: 5px 10px 7px - line-height: 20px - height: 20px - color: #eee - vertical-align: top - text-shadow: 0 1px 1px rgba(0,0,0,0.2) - &:hover - background: #303030 - color: white - @include box-shadow(0 1px 2px rgba(255,255,255,0.3) inset) - .oe_active - background: #303030 - font-weight: bold + display: block + padding: 5px 10px 7px + line-height: 20px + height: 20px + text-decoration: none + color: #eee + vertical-align: top + text-shadow: 0 1px 1px rgba(0,0,0,0.2) + @include transition(all 0.2s ease-out) + &:hover, + background: rgba(0,0,0,0.2) + text-shadow: black 0px 0px 3px color: white - @include box-shadow(0 1px 2px rgba(255,255,255,0.3) inset) + @include box-shadow(0 1px 2px rgba(0,0,0,0.4) inset) .oe_topbar_avatar width: 24px height: 24px margin: -2px 2px 0 0 - @include radius(4px) + @include radius(3px) .oe_topbar_avatar vertical-align: top - .oe_dropdown_toggle:after - width: 0 - height: 0 - display: inline-block - content: "&darr" - text-indent: -99999px - vertical-align: top - margin-top: 8px - margin-left: 4px - border-left: 4px solid transparent - border-right: 4px solid transparent + .oe_dropdown_arrow:after border-top: 4px solid white - @include opacity(0.5) + .oe_dropdown_menu + top: 32px + background: #333 + background: rgba(37,37,37,0.9) + border-color: #999 + border-color: rgba(0, 0, 0, 0.2) + border-style: solid + border-width: 0 1px 1px + @include radius(0 0 6px 6px) + @include background-clip() + li + float: none + padding: 3px 12px + a + color: #eee + &:hover + @include vertical-gradient(#292929, #191919) + @include box-shadow(none) + + // oe menu is the list of the buttons on the left side of the bar. + // So why aren't the buttons oe_topbar_items ? This sad state of affairs + // is a leftover from an uncomplete refactoring when the left and top menu + // were merged. You are welcome to refactor and clean this up + .oe_menu + float: left + padding: 0 + margin: 0 + font-size: 13px + > li + list-style-type: none + padding: 0 + margin: 0 + float: left + display: block + color: #eeeeee + > a + display: block + padding: 5px 10px 7px + line-height: 20px + height: 20px + text-decoration: none + color: #eee + vertical-align: top + text-shadow: 0 1px 1px rgba(0,0,0,0.2) + @include transition(all 0.2s ease-out) + &:hover, + background: rgba(0,0,0,0.2) + text-shadow: black 0px 0px 3px + color: white + @include box-shadow(0 1px 2px rgba(0,0,0,0.4) inset) + > .oe_active + background: rgba(0,0,0,0.2) + text-shadow: black 0px 0px 3px + font-weight: bold + @include box-shadow(0 1px 2px rgba(0,0,0,0.4) inset) + + .oe_user_menu + float: right + padding: 0 + margin: 0 + li + list-style-type: none + float: left + .oe_dropdown_menu + right: -1px + + .oe_systray > div + float: left + padding: 0 4px 0 4px + .oe_systray + float: right + // }}} // Webclient.leftbar {{{ .oe_leftbar display: none width: 220px background: #f0eeee - border-right: 1px solid #afafb6 + border-right: 1px solid $tag-border text-shadow: 0 1px 1px white padding-bottom: 16px - line-height: 18px a.oe_logo width: 220px display: block @@ -467,150 +931,22 @@ $colour4: #8a89ba color: #c81010 font-style: italic // }}} - // UserMenu {{{ - .oe_user_menu - float: right - padding: 0 - margin: 0 - li - list-style-type: none - float: left - .oe_dropdown - position: relative - - .oe_dropdown_options - float: left - background: #333 - background: rgba(37,37,37,0.9) - display: none - position: absolute - top: 32px - right: -1px - border: 0 - z-index: 900 - margin-left: 0 - margin-right: 0 - padding: 6px 0 - zoom: 1 - border-color: #999 - border-color: rgba(0, 0, 0, 0.2) - border-style: solid - border-width: 0 1px 1px - @include radius(0 0 6px 6px) - @include box-shadow(0 1px 4px rgba(0,0,0,0.3)) - @include background-clip() - li - float: none - display: block - background-color: none - a - white-space: nowrap - display: block - padding: 4px 15px - clear: both - font-weight: normal - line-height: 18px - color: #eee - &:hover - @include vertical-gradient(#292929, #191919) - @include box-shadow(none) - hr - border-top: 1px solid #999 - border-bottom: 0 - // }}} - // Systray {{{ - .oe_systray > div - float: left - padding: 0 4px 0 4px - .oe_systray - float: right + // Webclient.leftbar items {{{ - .oe_systray_dropdown - position: relative - .oe_systray_dropdown:hover .oe_systray_dropdown_options - display: block - .oe_systray_dropdown_options - background: #333 - background: rgba(37,37,37,0.9) - display: none - position: absolute - z-index: 900 - border: 0 - margin-left: 0 - margin-right: 0 - padding: 6px 0 - border-color: #999 - border-color: rgba(0, 0, 0, 0.2) - border-style: solid - border-width: 0 1px 1px - @include radius(0 0 6px 6px) - @include box-shadow(0 1px 4px rgba(0,0,0,0.3)) - @include background-clip() - li - float: none - display: block - background-color: none - a - display: block - padding: 4px 15px - clear: both - font-weight: normal - line-height: 18px - color: #eee - &:hover - @include vertical-gradient(#292929, #191919) - @include box-shadow(none) - // }}} - // Menu {{{ - .oe_menu - float: left - padding: 0 - margin: 0 - li - float: left - a - display: block - padding: 5px 10px 7px - line-height: 20px - height: 20px - color: #eee - vertical-align: top - text-shadow: 0 1px 1px rgba(0,0,0,0.2) - &:hover - background: #303030 - color: white - @include box-shadow(0 1px 2px rgba(255,255,255,0.3) inset) - .oe_active - background: #303030 - font-weight: bold - color: white - @include box-shadow(0 1px 2px rgba(255,255,255,0.3) inset) - .oe_menu_more_container - position: relative - .oe_menu_more - position: absolute - padding: 0 - background-color: #646060 - z-index: 1 - border: 1px solid black - border-bottom-left-radius: 5px - border-bottom-right-radius: 5px - li - float: none - a - white-space: nowrap .oe_secondary_menu_section font-weight: bold margin-left: 8px - color: $colour4 + color: $link-color .oe_secondary_submenu padding: 2px 0 8px 0 margin: 0 width: 100% display: inline-block - > li + li position: relative - padding: 1px 0 1px 20px + margin: 0 + padding: 1px 0 1px 20px !important + list-style-type: none a display: block color: #4c4c4c @@ -620,7 +956,7 @@ $colour4: #8a89ba top: 1px right: 1px font-size: 10px - background: $colour4 + background: $link-color color: white padding: 2px 4px margin: 1px 6px 0 0 @@ -628,32 +964,32 @@ $colour4: #8a89ba text-shadow: 0 1px 1px rgba(0,0,0,0.2) @include radius(4px) @include box-shadow(inset 0 1px 1px rgba(0, 0, 0, 0.2)) - .oe_menu_counter + .oe_menu_counter float: right - background: #8a89ba - color: #eee - font-size: 12px - border: 1px solid lightgray - padding: 0px 4px 0px 4px text-shadow: 0 1px 1px rgba(0,0,0,0.2) - margin: 0px - @include radius(4px) + margin: 1px + padding: 1px 4px + border: none + &:hover + cursor: pointer + @include transform(scale(1.1)) .oe_active - background: $colour4 border-top: 1px solid lightGray - border-bottom: 1px solid lightGray + border-bottom: 1px solid #dedede text-shadow: 0 1px 1px rgba(0,0,0,0.2) - @include box-shadow(inset 0 1px 1px rgba(0, 0, 0, 0.2)) + @include box-shadow((inset 0 1px 3px rgba(0, 0, 0, 0.2), inset 0 -1px 3px rgba(40, 40, 40, 0.2))) + background: $link-color a color: white .oe_menu_label background: #eee - color: $colour4 + color: $link-color text-shadow: 0 1px 1px white @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.2)) - .oe_menu_counter + .oe_menu_counter background: #eee - color: #8a89ba + color: $link-color + @include box-shadow(inset 0 1px 2px rgba(0, 0, 0, 0.2)) .oe_menu_toggler:before width: 0 height: 0 @@ -675,123 +1011,139 @@ $colour4: #8a89ba border-left: 4px solid transparent border-right: 4px solid transparent border-top: 4px solid #4c4c4c + + // }}} + // About openerp {{{ + .oe_about + background-color: white + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAKUlEQVQIHWO8e/fufwYsgAUkJigoiCIF5DMyoYggcUiXgNnBiGQKmAkARpcEQeriln4AAAAASUVORK5CYII=) + @include radius(0 0 2px 2px) + a + color: $link-color + &:hover + text-decoration: underline + &:focus + outline: none + .oe_logo + margin-left: -6px + .oe_bottom + position: absolute + top: 50% + left: 0 + right: 0 + bottom: 0 + text-shadow: 0 1px 1px #999999 + @include vertical-gradient(#b41616, #600606) + color: #eee + padding: 0 16px + @include radius(0 0 2px 2px) + a + color: #eee // }}} // ActionManager {{{ .oe_application width: 100% a - color: $colour4 + color: $link-color &:hover text-decoration: underline - // }}} - // Content Header MIT {{{ - .oe-view-manager-header - border-top: 1px solid #cacaca - border-bottom: 1px solid #cacaca - @include vertical-gradient(#fcfcfc, #dedede) - @include box-shadow((0 1px 0 rgba(255,255,255,0.4), 0 0 9px rgba(0,0,0,0.1))) - h2 - float: left - font-size: 18px - margin: 1px 0 - a - color: $colour4 - .buttons - padding-right: 8px - li - float: left - margin-right: 12px - .filter - vertical-align: top - li - height: 24px - line-height: 24px - padding: 0 8px - .header-row - height: 26px - line-height: 26px - margin: 8px - clear: both - text-shadow: 0 1px 1px white - .dropdown-toggle:after - margin-top: 7px - margin-left: 6px - border-top-color: #404040 - .oe_vm_switch - li - padding-left: 1px - text-align: center - width: 24px - height: 24px - line-height: 24px - .pagination - li - height: 24px - line-height: 24px - padding: 0 8px - .button-group + > div + height: 100% + .oe_breadcrumb_item:not(:last-child) display: inline-block - border: 1px solid #ababab - @include radius(5px) - li - float: left - border-right: 1px solid #ababab - &:last-child - border: none - a - color: #4c4c4c - &:hover - text-decoration: none - .active - width: 100% - background: #999 - @include box-shadow(0 1px 4px rgba(0,0,0,0.3) inset) - color: #fff - text-shadow: 0 1px 2px rgba(0,0,0,0.4) + max-width: 7em + white-space: nowrap + overflow: hidden + text-overflow: ellipsis // }}} - // ViewManager.header {{{ - .oe_view_manager_header - width: 100% - border-top: 1px solid #cacaca - border-bottom: 1px solid #cacaca - @include vertical-gradient(#fcfcfc, #dedede) - @include box-shadow((0 1px 0 rgba(255,255,255,0.4), 0 0 9px rgba(0,0,0,0.1))) - .oe_header_row - //min-height: 26px - //line-height: 26px - clear: both - text-shadow: 0 1px 1px white - td - padding: 8px - .oe_header_row:last-child - td - padding-top: 0 - .oe_header_row:first-child + // ViewManager common {{{ + .oe_view_manager + .oe_view_manager_body + height: inherit + .oe_view_manager_view_kanban + height: inherit + + table.oe_view_manager_header + width: 100% + table-layout: fixed + .oe_header_row + //min-height: 26px + //line-height: 26px + clear: both + text-shadow: 0 1px 1px white + .oe_header_row:last-child + td + padding-top: 0 + .oe_header_row:first-child + td + padding-top: 8px + .oe_view_manager_sidebar + margin: 0px auto + text-align: center td - padding-top: 8px - .oe_view_manager_sidebar - margin: 0px auto - width: 400px - text-align: center - td + line-height: 26px + h2 + font-size: 18px + margin: 0 + float: left + a + color: $link-color + .oe_dropdown_menu + line-height: normal + .oe_button_group + display: inline-block + border: 1px solid #ababab + @include radius(5px) + li + float: left + border-right: 1px solid #ababab + &:last-child + border: none + a + color: #4c4c4c + &:hover + text-decoration: none + .active + background: #999 + @include box-shadow(0 1px 4px rgba(0,0,0,0.3) inset) + a + color: #fff + text-shadow: 0 1px 2px rgba(0,0,0,0.4) + .oe_view_manager_buttons + white-space: nowrap + // }}} + // ViewManager.pager {{{ + .oe_view_manager_pager line-height: 26px - h2 - font-size: 18px + .oe_list_pager_single_page .oe_pager_group + display: none + .oe_pager_value + float: left + margin-right: 8px + ul.oe_pager_group + padding: 0 margin: 0 + .oe_pager_group float: left - a - color: $colour4 - .oe_button_group + height: 24px + line-height: 24px display: inline-block border: 1px solid #ababab + cursor: pointer @include radius(5px) li + height: 24px + line-height: 24px + padding: 0 + margin: 0 + list-style-type: none float: left border-right: 1px solid #ababab &:last-child border: none a color: #4c4c4c + padding: 0 8px &:hover text-decoration: none .active @@ -800,128 +1152,70 @@ $colour4: #8a89ba a color: #fff text-shadow: 0 1px 2px rgba(0,0,0,0.4) - .oe_view_manager_buttons - white-space: nowrap - // }}} - // ViewManager.pager {{{ - .oe_view_manager_pager - line-height: 26px - .oe_pager_value - float: left - margin-right: 8px - .oe_pager_group - float: left - height: 24px - line-height: 24px - display: inline-block - border: 1px solid #ababab - cursor: pointer - @include radius(5px) - li - height: 24px - line-height: 24px + // }}} + // ViewManager.switches {{{ + .oe_view_manager_switch padding: 0 - float: left - border-right: 1px solid #ababab - &:last-child - border: none - a - color: #4c4c4c - padding: 0 8px - &:hover - text-decoration: none - .active - background: #999 - @include box-shadow(0 1px 4px rgba(0,0,0,0.3) inset) - a - color: #fff - text-shadow: 0 1px 2px rgba(0,0,0,0.4) - // }}} - // ViewManager.switches {{{ - .oe_view_manager_switch - li - text-align: center - width: 24px - height: 24px - line-height: 16px - a + margin: 0 0 0 8px + li + margin: 0 + width: 24px + height: 24px + line-height: 16px + padding: 0 + text-align: center + list-style-type: none + a + position: relative + .oe_vm_switch_list:after, .oe_vm_switch_tree:after + padding: 2px + content: "i" + .oe_vm_switch_form:after + content: "m" + .oe_vm_switch_graph:after + font-family: "mnmliconsRegular" !important + font-size: 21px + font-weight: 300 !important + content: "}" + top: -2px position: relative - .oe_vm_switch_list:after, .oe_vm_switch_tree:after - padding: 2px - content: "i" - .oe_vm_switch_graph:after - font-family: "mnmliconsRegular" !important - font-size: 21px - font-weight: 300 !important - content: "}" - top: -2px - position: relative - .oe_vm_switch_gantt:after - font-family: "mnmliconsRegular" !important - font-size: 21px - font-weight: 300 !important - content: "y" - top: -2px - position: relative - .oe_vm_switch_calendar:after - content: "P" - .oe_vm_switch_kanban:after - content: "k" - .oe_vm_switch_diagram:after - content: "f" + .oe_vm_switch_gantt:after + font-family: "mnmliconsRegular" !important + font-size: 21px + font-weight: 300 !important + content: "y" + top: -2px + position: relative + .oe_vm_switch_calendar:after + content: "P" + .oe_vm_switch_kanban:after + content: "k" + .oe_vm_switch_diagram:after + content: "f" + // }}} + // ViewManager application {{{ + .oe_view_manager_current + height: 100% + > .oe_view_manager_header + border-bottom: 1px solid #cacaca + @include vertical-gradient(#fcfcfc, #dedede) + @include box-shadow((0 1px 0 rgba(255,255,255,0.4), 0 0 9px rgba(0,0,0,0.1))) + .oe_header_row + td + padding: 8px + .oe_header_row:first-child + td + padding-top: 8px // }}} - // ViewManager.sidebar {{{ - .oe_form_dropdown_section - position: relative - display: inline-block - .oe_dropdown_toggle:after - width: 0 - height: 0 - display: inline-block - content: "&darr" - text-indent: -99999px - vertical-align: top - border-left: 4px solid transparent - border-right: 4px solid transparent - border-top: 4px solid white - @include opacity(0.5) - margin-top: 7px - margin-left: 6px - border-top-color: #404040 - .oe_dropdown_menu - display: none - position: absolute - top: 28px - left: 0px - padding: 8px - border: 1px solid #afafb6 - min-width: 120px - overflow-x: hidden - z-index: 900 - text-align: left - background: white - @include radius(3px) - @include box-shadow(0 1px 4px rgba(0,0,0,0.3)) - li - list-style-type: none - float: none - display: block - background-color: none - a - display: block - padding: 3px 6px - clear: both - font-weight: normal - line-height: 14px - color: #4c4c4c - text-decoration: none - cursor: pointer - &:hover - text-decoration: none - @include vertical-gradient(#f0f0fa, #eeeef6) - @include box-shadow(none) - .oe_sidebar - white-space: nowrap + // ViewManager inline {{{ + .oe_view_manager_inline, .oe_view_manager_inlineview + height: 100% + > .oe_view_manager_header + display: none + // }}} + // Viewmanager popup {{{ + .oe_popup_form > .oe_formview > .oe_form_pager + display: none !important // }}} // SearchView {{{ .oe_searchview @@ -930,7 +1224,7 @@ $colour4: #8a89ba float: right padding: 1px 0 line-height: 18px - width: 480px + width: 400px border: 1px solid #ababab background: white @include radius(13px) @@ -938,13 +1232,11 @@ $colour4: #8a89ba input, textarea padding: 3px height: 14px - font-size: 11px + font-size: 12px line-height: 18px - border: 1px solid #ccc - @include radius(3px) &.oe_focused - border-color: $facets-border-selected - @include box-shadow(0 1px 2px $facets-border-selected inset) + border-color: $tag-border-selected + @include box-shadow(0 1px 2px $tag-border-selected inset) .oe_searchview_clear cursor: pointer position: absolute @@ -977,42 +1269,46 @@ $colour4: #8a89ba border-right: 5px solid transparent @include opacity() + .oe_searchview_search + @include text-to-icon("r", #a3a3a3) + @include box-shadow(none) + @include radius(0) + position: absolute + left: 3px + top: 1px + padding: 0 + border: none + background: transparent + .oe_searchview_facets min-height: 22px - &:before - color: #ccc - font-family: "mnmliconsRegular" - content: "r" - font-size: 130% - display: inline - position: relative - left: 6px - top: 2px - color: #a3a3a3 - padding-right: 4px + margin-left: 15px * vertical-align: top display: inline-block line-height: 17px - .oe_searchview_input, .oe_searchview_facet + .oe_searchview_facet height: 18px margin: 1px 0 font-size: 11px &:focus outline: none .oe_searchview_input - padding: 0 3px + padding: 0 0 0 6px + font-size: 12px + height: 16px + margin-top: 3px + &:focus + outline: none .oe_searchview_facet position: relative cursor: pointer - border: 1px solid $facets-border - @include radius(3px) - background: #8786b7 + padding: 0 -webkit-font-smoothing: auto // spacing for opera, FF &:focus - border-color: $facets-border-selected - @include box-shadow(0 0 3px 1px $facets-border-selected) + border-color: $tag-border-selected + @include box-shadow(0 0 3px 1px $tag-border-selected) .oe_facet_values background: #f0f0fa @include radius(0 3px 3px 0) @@ -1025,8 +1321,9 @@ $colour4: #8a89ba .oe_facet_category.oe_i font-size: 16px .oe_facet_value - border-left: 1px solid $facets-border + border-left: 1px solid $tag-border text-shadow: 0 1px 1px white + color: #4C4C4C &:last-child padding-right: 16px .oe_facet_remove @@ -1037,6 +1334,7 @@ $colour4: #8a89ba line-height: 8px width: 12px height: 12px + padding-top: 1px text-align: center font-weight: bold cursor: pointer @@ -1052,7 +1350,7 @@ $colour4: #8a89ba .oe_searchview_drawer position: absolute - z-index: 1 + z-index: 100 // detach drawer from field slightly margin-top: 4px top: 100% @@ -1060,7 +1358,7 @@ $colour4: #8a89ba background-color: white min-width: 100% display: none - border: 1px solid #afafb6 + border: 1px solid $tag-border text-align: left @include radius(4px) @include box-shadow(0 1px 4px rgba(0,0,0,0.3)) @@ -1097,7 +1395,7 @@ $colour4: #8a89ba display: table width: 100% > div - @include unscrew-box + @include box-sizing(border) display: table-cell width: 50% ul @@ -1123,13 +1421,13 @@ $colour4: #8a89ba // after oe_selected so background color is not overridden &:hover background-color: $hover-background - form - margin-left: 12px - p - margin: 4px 0 - line-height: 18px - button - margin: 0 0 8px 0 + form + margin-left: 12px + p + margin: 4px 0 + line-height: 18px + button + margin: 0 0 8px 0 .oe_searchview_custom padding: 0 8px 8px 8px form @@ -1145,6 +1443,11 @@ $colour4: #8a89ba position: absolute top: 0 right: 5px + .oe_searchview_dashboard + form + display: none + margin-top: 2px + .oe_searchview_advanced form display: none @@ -1172,13 +1475,12 @@ $colour4: #8a89ba display: block // delete buttons - .oe_searchview_custom_delete - position: absolute - left: 4px - top: 3px .oe_searchview_custom_delete, .searchview_extended_delete_prop + display: inline-block width: 12px height: 12px + line-height: 12px + padding: 1px color: #8786b7 line-height: 8px text-align: center @@ -1190,172 +1492,185 @@ $colour4: #8a89ba background: #8786b7 text-shadow: 0 1px 1px rgba(0, 0, 0, 0.4) @include radius(2px) + .oe_searchview_custom_delete + display: none + position: absolute + bottom: 1px + right: 4px + .oe_searchview_custom_private, .oe_searchview_custom_public + &:hover + .oe_searchview_custom_delete + display: inline-block + .oe_searchview_custom_public:after + content: "," + font-family: "entypoRegular" !important + font-size: 22px + font-weight: 300 !important + margin: 0 0 0 4px + padding: 0 + + // }}} // Views Common {{{ .oe_view_nocontent - > img - float: left - margin: 1.5em - > div - // don't encroach on my arrow - overflow: hidden - padding: 35px 0px 0px 0px - max-width: 700px - font-size: 125% - .oe_view_topbar - border-bottom: 1px solid #cacaca - @include vertical-gradient(#fcfcfc, #dedede) - padding: 0 8px - line-height: 30px + padding: 15px + margin-top: 0 + color: #777777 + font-size: 125% + max-width: 700px + .oe_view_nocontent_create + background: transparent url(/web/static/src/img/view_empty_arrow.png) no-repeat 7px 0 + margin-top: 0 + padding-top: 35px + min-height: 28px + color: #4c4c4c + > p + padding-left: 95px + .oe_empty_custom_dashboard + background: transparent url(/web/static/src/img/graph_background.png) no-repeat 0 0 + margin-top: -15px + padding: 100px 0 0 137px + min-height: 327px + margin-left: -15px + // }}} - // FormView.body {{{ + // FormView.base and dynamic tags {{{ .oe_formview background: white - .oe_form_header - padding: 8px + .oe_form_dropdown_section + position: relative + display: inline-block .oe_form_invalid input, select, textarea background-color: #F66 !important border: 1px solid #D00 !important - .oe_form_button_save_dirty - color: white - background: #dc5f59 - background: -moz-linear-gradient(#dc5f59, #b33630) - background: -webkit-gradient(linear, left top, left bottom, from(#dc5f59), to(#b33630)) - background: -webkit-linear-gradient(#dc5f59, #b33630) - -moz-box-shadow: none - -webkit-box-shadow: none - -box-shadow: none - font-weight: bold - &:hover - background: #ED6F6A - // }}} - // FormView.customdivs {{{ - .oe_form header - position: relative - border-bottom: 1px solid #cacaca - @include vertical-gradient(#fcfcfc, #dedede) - padding: 0 8px - line-height: 30px - button - font-size: 12px - float: left - height: 24px !important - line-height: 24px - vertical-align: top - padding: 0 10px - margin: 3px 4px 3px 0 - span.i - line-height: 20px - height: 24px - ul - height: 30px - padding: 0 - margin: 0 - text-shadow: 0 1px 1px white - border-left: 1px solid #cacaca - border-right: 1px solid #cacaca - li - padding: 0 - margin: 0 - float: left - vertical-align: top - border-right: 1px solid #cacaca - height: 30px - padding: 0 12px - &:first-child - border-left: 1px solid #cacaca - a - color: #4c4c4c - &:hover - color: black - .oe_form_steps - display: inline-block - float: right - img - margin: 0 8px - vertical-align: top - li - border-right: none - padding: 0 - &:first-child - margin-left: 12px - border-left: none - &:last-child - margin-right: 12px - .oe_form_steps_active - font-weight: bold - color: #b33630 + .oe_view_manager_current .oe_form_editable + .oe_highlight + color: #404040 + background: none + button.oe_highlight + @include vertical-gradient(#efefef, #d8d8d8) + @include box-shadow((0 1px 2px rgba(0, 0, 0, .1), 0 1px 1px rgba(255, 255, 255, .8) inset)) + button.oe_highlight:active + @include vertical-gradient(#e3e3e3, #f6f6f6) + @include box-shadow(none) + button.oe_highlight:hover + @include vertical-gradient(#f6f6f6, #e3e3e3) + @include box-shadow((0 1px 2px rgba(0, 0, 0, .1), 0 1px 1px rgba(255, 255, 255, .8) inset)) + .oe_form_invisible + display: none !important .oe_form_readonly - button.oe_form_button_hi - color: white - background: #DC5F59 - background: -moz-linear-gradient(#DC5F59, #B33630) - background: -webkit-gradient(linear, left top, left bottom, from(#DC5F59), to(#B33630)) - background: -webkit-linear-gradient(#DC5F59, #B33630) - -moz-box-shadow: none - -webkit-box-shadow: none - -box-shadow: none - button.oe_form_button_hi:hover - background: #DF6B66 - background: -moz-linear-gradient( #DF6B66, #BF3A33) - background: -webkit-gradient(linear, left top, left bottom, from( #DF6B66), to( #BF3A33)) - background: -webkit-linear-gradient( #DF6B66, #BF3A33) - -moz-box-shadow: 0 0 1px rgba(0, 0, 0, 0.2) - -webkit-box-shadow: 0 0 1px rgba(0, 0, 0, 0.2) - -box-shadow: 0 0 1px rgba(0, 0, 0, 0.2) + .oe_edit_only, .oe_form_field:empty + display: none !important + .oe_form .oe_form_field_date + width: auto + // Sheet and padding + .oe_form_nosheet + margin: 16px + > header + margin: -16px -16px 0 -16px + padding: 0 + .oe_form_sheetbg + padding: 16px 0 + .oe_form_sheet_width + min-width: 650px + max-width: $sheet-max-width + margin: 0 auto + .oe_form_sheet + background: white + min-height: 330px + padding: 16px + // Sheet inline mode .oe_application .oe_form_sheetbg background: url(/web/static/src/img/form_sheetbg.png) - padding: 8px 0 - border-bottom: 1px solid #ddd - .oe_form_sheet_width, footer + border-bottom: 1px solid #ddd + .oe_form_sheet + border: 1px solid #c8c8d3 //$tag-border + @include box-shadow(0 4px 20px rgba(0,0,0,0.15)) + .ui-tabs + margin: 0 -16px + .oe_notebook_page + padding: 0 16px + // }}} + // FormView.custom tags and classes {{{ + .oe_form + header + position: relative + border-bottom: 1px solid #cacaca + padding-left: 2px + @include vertical-gradient(#fcfcfc, #dedede) + > span + margin-left: 4px + ul + display: inline-block + float: right + .oe_form_button + margin: 3px 2px 1px + .oe_tags + margin: 5px 0 0 5px + width: 400px + padding-bottom: 0 + div.oe_chatter min-width: 650px - max-width: 960px + max-width: $sheet-max-width margin: 0 auto - .oe_form_sheet - padding: 18px - background: white - min-height: 330px - border: 1px solid #afafb6 - @include box-shadow(0 0 10px rgba(0,0,0,0.3)) + padding: 16px 0 48px + .oe_grey + color: #aaa + max-width: 650px + margin: 0 0 10px 0 + div.oe_form_configuration + p, ul, ol + color: #aaa + max-width: 650px + label + min-width: 150px + .oe_form_group_cell_label + padding: 1px 0 + .oe_form_group_cell div div + padding: 1px 0 + .oe_subtotal_footer + width: 1% !important + td.oe_form_group_cell + text-align: right + padding: 0 !important + td.oe_form_group_cell_label + border-right: none + .oe_subtotal_footer_separator + width: 108px + border-top: 1px solid #cacaca + margin-top: 4px + padding-top: 4px + font-weight: bold + font-size: 18px + label:after + content: ":" + label.oe_subtotal_footer_separator + font-weight: bold !important + padding: 2px 11px 2px 0px !important + label.oe_form_label_help + font-weight: normal + // }}} - // FormView.layout {{{ - .oe_form_invisible - display: none - .oe_form_editable .oe_form_editable_hidden - display: none - .oe_form_readonly - .oe_form_readonly_hidden, .oe_form_field:empty - display: none + // FormView.group {{{ .oe_form - .oe_form_field_text + .oe_form_button + margin: 2px + td.oe_form_group_cell_label + border-right: 1px solid #ddd + padding: 2px 0px + label + line-height: 18px + display: block + min-width: 140px + td.oe_form_group_cell + .oe_form_group_cell + padding: 2px 0 2px 8px + .oe_form_group width: 100% - - .oe_form_field_char input, - .oe_form_field_text textarea, - .oe_form_field_url input, - .oe_form_field_selection select - width: inherit - - .oe_form_field_float input - width: 100px - - h1 .oe_form_field_float input - width: 140px - - h2 .oe_form_field_float input - width: 120px - - h1, h2, h3, h4, h5, h6 - margin: 0 0 4px 0 - input - height: inherit !important - font-size: inherit - .oe_form_nosheet > *:not(.oe_form_topbar) - margin-left: 10px !important - margin-right: 10px !important - + margin: 9px 0 9px 0 + .oe_form_group_cell.oe_group_right + padding-left: 20px // }}} // FormView.label {{{ .oe_form @@ -1371,57 +1686,73 @@ $colour4: #8a89ba top: -4px padding: 0 2px // }}} + // FormView.separator {{{ + .oe_horizontal_border + border-bottom: 1px solid black + .oe_horizontal_separator + font-weight: bold + font-size: 20px + margin: 15px 0px 10px 0px + color: $section-title-color + .oe_horizontal_separator:empty + height: 5px + .oe_vertical_separator + border-left: 1px solid #666 + padding: 0 4px 0 4px + // }}} + // FormView.progressbar {{{ + .oe_form_field_progressbar + display: inline-block + min-width: 70px + .oe_form_field_progressbar.ui-progressbar + height: 22px + font-size: 10px + @include box-sizing(border) + border: 1px solid #999 + @include radius(3px) + background: white + min-width: 50px + span + position: absolute + margin-left: 10px + font-weight: bold + .ui-widget-header + background: #cccccc url(/web/static/lib/jquery.ui/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x + // }}} // FormView.fields {{{ .oe_form - .oe_form_field_text textarea - resize: vertical + .oe_form_field_text + width: 100% + .oe_form_field_char input, + .oe_form_field_url input, + .oe_form_field_email input, + .oe_form_field_text textarea, + .oe_form_field_selection select + width: 100% + .oe_form_field_text.oe_inline, .oe_form_field_text.oe_inline > textarea + width: 500px + h1, h2, h3, h4, h5, h6 + margin: 0 0 4px 0 + input + height: inherit !important + font-size: inherit .oe_form_field width: 100% display: inline-block padding: 2px 2px 2px 0px - line-height: 18px input margin: 0px - - .oe_form_field_integer - width: 180px - .oe_form_field_float - width: 180px - .oe_form_field_date - width: 7em - .oe_form_field_datetime - width: 11em - .oe_form_inline - width: auto - - input[type="text"], - input[type="password"], - input[type="file"], - select + input[type="text"], input[type="password"], input[type="file"], select height: 22px padding-top: 2px - input[type="text"], - input[type="password"], - input[type="file"], - select, - textarea - -moz-box-sizing: border-box - -webkit-box-sizing: border-box - -ms-box-sizing: border-box - box-sizing: border-box - padding: 0 2px - border: 1px solid #999 - -moz-border-radius: 3px - -webkit-border-radius: 3px - border-radius: 3px + input[type="text"], input[type="password"], input[type="file"], select, textarea + @include box-sizing(border) background: white - min-width: 70px + min-width: 60px color: #1f1f1f - input[readonly], - select[readonly], - textarea[readonly], - input[disabled], - select[disabled] + textarea + height: 32px + input[readonly], select[readonly], textarea[readonly], input[disabled], select[disabled] background: #E5E5E5 !important color: #666 textarea[disabled] @@ -1430,19 +1761,6 @@ $colour4: #8a89ba padding-left: 8px @include box-shadow(none) @include radius(0px) - .oe_form_field_many2one input, - .oe_form_field_binary input, - .oe_form_field_binary input, - .oe_form_field_email input, - .oe_form_field_url input - border-right: none - -webkit-border-top-right-radius: 0px - -webkit-border-bottom-right-radius: 0px - -moz-border-radius-topright: 0px - -moz-border-radius-bottomright: 0px - border-top-right-radius: 0px - border-bottom-right-radius: 0px - .oe_form_field_email button img, .oe_form_field_url button img vertical-align: top .oe_form_field_date, @@ -1450,26 +1768,7 @@ $colour4: #8a89ba white-space: nowrap .oe_form_field_boolean padding-top: 4px - .oe_form_field_many2manytags - .text-wrap - width: 100% !important - textarea - width: 100% !important - .oe_form_field_many2manytags_box - border-radius: 2px - box-sizing: border-box - position: relative - float: left - border: 1px solid #9DACCC - background: #E2E6F0 - color: black - padding: 0px 3px 0px 3px - margin: 0 2px 2px 0 - height: 16px - font: 11px "lucida grande", tahoma, verdana, arial, sans-serif - .text-core .text-wrap .text-dropdown .text-list .text-suggestion em - font-style: italic - text-decoration: none + width: auto .oe_datepicker_container display: none .oe_datepicker_root @@ -1492,90 +1791,62 @@ $colour4: #8a89ba filter: alpha(opacity=50) right: 5px top: 3px - .oe_form_group_cell .oe_kanban_view - // Hack due to absolute positioning of kanban view messing with o2m - position: static - .oe_form_field_with_button - input - width: 100% + .oe_form_field_with_button.oe_no_button > .oe_button + display: none + .oe_form_field_with_button:not(.oe_no_button) > .oe_button float: right - -webkit-border-top-left-radius: 0px - -webkit-border-bottom-left-radius: 0px - -moz-border-radius-topleft: 0px - -moz-border-radius-bottomleft: 0px - border-top-left-radius: 0px + @include radius(0) border-bottom-left-radius: 0px height: 22px + .oe_form_field_with_button + input + width: 100% > div position: relative overflow: hidden - - .oe_form_title - width: 55% - .oe_form_title:after - content: "." - display: block - height: 0 - clear: both - visibility: hidden - .oe_form_button_box - width: 200px - text-align: center - - .oe_form_group - margin: 5px 0px 5px 0px - - .oe_form_group_cell .oe_kanban_view - // Hack due to absolute positioning of kanban view messing with o2m - position: static - - // }}} - // FormView.buttons {{{ - .oe_form - .oe_form_button.oe_button - height: 22px - white-space: nowrap - span - position: relative - vertical-align: top - .oe_form_button > img - vertical-align: -3px - padding: 0 2px - // From FP: .oe_form_group_cell > .oe_form_button.oe_button - // From FP: display: block - // From FP: white-space: nowrap - // From FP: min-width: 100% - // From FP: width: 100% - + .oe_form_field_html + position: relative + width: 600px + margin-left: 130px + margin-top: 32px + margin-bottom: 32px + text-align: justify + + .oe_form_editable + .oe_form + .oe_form_field_integer + width: 6em !important + .oe_form_field_float + width: 7em !important + .oe_form_field_date + width: 7.5em !important + .oe_form_field_datetime + width: 11.5em !important // }}} - // FormView.binaryfile {{{ + // FormView.fields_binary {{{ /* http://www.quirksmode.org/dom/inputfile.html * http://stackoverflow.com/questions/2855589/replace-input-type-file-by-an-image */ - .oe_form - .oe-binary-file-set - overflow: hidden - position: relative - display: inline-block - width: 45px - height: 30px - input.oe-binary-file + .oe_hidden_input_file + // Position: relative is used for the hidden input[type=file] + // Do not remove it anymore ! + position: relative + input.oe_form_binary_file z-index: 0 line-height: 0 font-size: 12px position: absolute - // Should be adjusted for all browsers top: 1px - right: 10px + left: 0 + right: 0 opacity: 0 filter: alpha(opacity = 0) -ms-filter: "alpha(opacity=0)" margin: 0 padding: 0 + .oe_form .oe_form_field_image - > img - min-width: 100px padding: 0 position: relative display: inline-block @@ -1584,202 +1855,271 @@ $colour4: #8a89ba .oe_form_field_image_controls position: absolute top: 1px - padding: 3px 0 0 0 - margin: 0 1px - @include opacity(0.7) - width: 99% + padding: 4px + width: 100% + display: none text-align: center - background: url(/web/static/src/img/form_sheetbg.png) - border-bottom: 1px dotted black - -moz-box-sizing: border-box - -webkit-box-sizing: border-box - -ms-box-sizing: border-box - box-sizing: border-box + color: #eee + background: rgba(37,37,37,0.9) + @include radius(3px 3px 0 0) + @include box-sizing(border) &:hover .oe_form_field_image_controls - @include opacity(1) - // }}} - // FormView.separator {{{ - .oe_horizontal_border - border-bottom: 1px solid black - .oe_horizontal_separator - font-weight: bold - font-size: 20px - margin: 20px 0px 10px 0px - color: #aab - .oe_horizontal_separator:empty - height: 5px - .oe_vertical_separator - border-left: 1px solid #666 - padding: 0 4px 0 4px + display: block // }}} - // FormView.notebook {{{ - .oe_form_notebook - margin: 8px 0 - padding: 0 8px - list-style: none - zoom: 1 - .oe_form_notebook:before, .oe_form_notebook:after - display: table - content: "" - zoom: 1 - .oe_form_notebook:after - clear: both - .oe_form_notebook > li - float: left - .oe_form_notebook > li > a - display: block - color: #4c4c4c - .oe_form_notebook - border-color: #ddd - border-style: solid - border-width: 0 0 1px - .oe_form_notebook > li - position: relative - margin-bottom: -1px - .oe_form_notebook > li > a - padding: 0 12px - margin-right: 2px - line-height: 30px - border: 1px solid transparent - @include radius(4px 4px 0 0) - .oe_form_notebook > li > a:hover - text-decoration: none - background-color: #eee - border-color: #eee #eee #ddd - .oe_form_notebook > li.ui-state-active > a, .oe_form_notebook > li.ui-state-active > a:hover - background-color: #ffffff - border: 1px solid #ddd - border-bottom-color: transparent - cursor: default - .oe_form_notebook_page - padding: 0 - div.ui-tabs - padding: 3px 0px 3px 0px - .ui-tabs-hide - display: none - + // FormView.many2one {{{ + .oe_form_field_many2one + td:first-child + position: relative + span.oe_m2o_drop_down_button + position: absolute + top: 2px + right: 0px + .oe_m2o_cm_button + line-height: 14px + float: right + padding-left: 2px + &.ui-autocomplete + li.oe_m2o_dropdown_option a + font-style: italic + padding-left: 2em + li:not(.oe_m2o_dropdown_option) + li.oe_m2o_dropdown_option + margin-top: 10px // }}} - // FormView.progressbar {{{ - .oe_form - .oe_form_field_progressbar + // FormView.fieldstatus {{{ + ul.oe_form_status, ul.oe_form_status_clickable + display: inline-block + margin: 0 + padding: 0 18px 0 0 + li display: inline-block - min-width: 70px - .oe_form_field_progressbar.ui-progressbar - height: 22px - font-size: 10px - -moz-box-sizing: border-box - -webkit-box-sizing: border-box - -ms-box-sizing: border-box - box-sizing: border-box - border: 1px solid #999 - -moz-border-radius: 3px - -webkit-border-radius: 3px - border-radius: 3px - background: white - min-width: 50px - - span - position: absolute - margin-left: 10px - font-weight: bold - - .ui-widget-header - background: #cccccc url(/web/static/lib/jquery.ui/css/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x + list-style-type: none + // It is probably a bad idea to modify this margin + margin: 0 -18px 0 0 + padding: 0 + @include vertical-gradient(#fcfcfc, #dedede) + &:first-child .label + border-left: 1px solid #cacaca + padding-left: 14px + &:last-child + border-right: 1px solid #cacaca + .label + padding-right: 14px + .arrow + display: none + .label + color: #4c4c4c + text-shadow: 0 1px 1px #fcfcfc, 0 -1px 1px #dedede + padding: 7px + display: inline-block + padding-left: 24px + margin: 0 + position: relative + .arrow + width: 17px + display: inline-block + vertical-align: top + overflow: hidden + margin-left: -5px + span + position: relative + width: 24px + height: 24px + display: inline-block + margin-left: -12px + margin-top: 3px + box-shadow: -1px 1px 2px rgba(255,255,255,0.2), inset -1px 1px 1px rgba(0,0,0,0.2) + @include skew-gradient(#fcfcfc, #dedede) + @include radius(3px) + @include transform(rotate(45deg)) + li.oe_active + @include vertical-gradient(#729fcf, #3465a4) + .arrow span + @include skew-gradient(#729fcf, #3465a4) + .label + color: white + text-shadow: 0 1px 1px #729fcf, 0 -1px 1px #3465a4 + ul.oe_form_status_clickable + li + cursor: pointer + &:hover + @include vertical-gradient(darken(#fcfcfc, 8%), darken(#dedede, 8%)) + .label + text-shadow: 0 -1px 1px #fcfcfc, 0 1px 1px #dedede + .arrow span + @include vertical-gradient(darken(#fcfcfc, 8%), darken(#dedede, 8%)) + .label + color: $link-color + li.oe_active + &:hover + @include vertical-gradient(darken(#729fcf, 10%), darken(#3465a4, 10%)) + .label + text-shadow: 0 -1px 1px #729fcf, 0 1px 1px #3465a4 + .arrow span + @include skew-gradient(darken(#729fcf, 10%), darken(#3465a4, 10%)) // }}} - // FormView.viewmanager {{{ - .oe_form - .oe_view_manager_header2 - td - padding: 0px 8px - line-height: 16px - .oe_i - font-size: 13px - .oe_pager_group - height: auto + // FormView.one2many {{{ + .oe_form .oe_form_field_one2many > .oe_view_manager + .oe_list_pager_single_page + display: none + .oe_form_field_one2many,.oe_form_field_many2many + // TODO: oe_form_field_one2many_list? + > .oe_view_manager + .oe_list_pager_single_page + display: none !important + .oe_form_field_one2many_list_row_add + font-weight: bold + .oe_list_content + > thead + border-bottom: 1px + > tbody + tr:nth-child(odd) + background: transparent + .oe_list .oe_list_edit_row_save + background: url(/web/static/src/img/iconset-b-remove.png) 50% 50% no-repeat + &:before + visibility: hidden + + &> .oe_view_manager + .oe_header_row_top + display: none + .oe_view_manager_header2 + td + padding: 0px 8px line-height: 16px - li + .oe_i + font-size: 13px + .oe_pager_group height: auto line-height: 16px - .oe-select-create-popup-view-form > .oe_formview > .oe_form_pager - display: none + li + height: auto + line-height: 16px + .oe_list_buttons.oe_editing .oe_list_save + // keep "save row" button hidden in o2m + visibility: hidden + .oe_form_editable + .oe_list_editable + .oe_list_content + td.oe_required + background-color: #d2d2ff + td.oe_readonly + background-color: #eee + $row-height: 27px + .oe_list_editable + .oe_list_content + td.oe_list_field_cell + padding: 4px 6px 3px 6px + .oe_list.oe_list_editable + td.oe_list_record_delete + position: absolute + .oe_list.oe_list_editable.oe_editing + .oe_m2o_drop_down_button + top: 5px + .oe_m2o_cm_button + display: none + .oe_form_field + input, textarea + height: $row-height + input, textarea + @include radius(0) + border: 1px solid #aaf + margin: 0 + &.oe_form_field_float,&.oe_form_view_integer + input + text-align: right + width: 100% + &.oe_form_field_datetime,&.oe_form_field_date + > span + width: 100% + input.oe_datepicker_master + width: 100% !important + .oe_form_field_float,.oe_form_view_integer,&.oe_form_field_datetime,&.oe_form_field_date + min-width: 0 !important + max-width: none !important // }}} - // FormView.classes for openerp views {{{ - .oe_form - td.oe_form_group_cell_label - border-right: 1px solid #ddd - padding: 2px 0px 2px 0px - label - line-height: 18px - display: block - min-width: 120px - td.oe_form_group_cell + .oe_form_group_cell - padding-left: 8px - .oe_form_subtotal_footer - width: auto - float: right - td.oe_form_group_cell - text-align: right + // FormView.many2many {{{ + .oe_form .oe_form_field_many2many > .oe_list + .oe_list_pager_single_page + display: none + // }}} + // ListView {{{ + .oe_list_buttons + .oe_alternative + visibility: hidden + .oe_list_save, .oe_list_discard + display: none + &.oe_editing + .oe_list_add + display: none + .oe_list_save + display: inline-block + .oe_list_discard + display: inline + .oe_alternative + visibility: visible + + .oe_list + position: relative + .oe_form + .oe_form_nosheet + margin: 0 // FIXME: either class or border should not be by default padding: 0 - td.oe_form_group_cell_label border: none .oe_form_field - width: auto !important - .oe_form_subtotal_footer_separator - border-top: 1px solid #cacaca - font-size: 120% - font-weight: bold - label.oe_form_subtotal_footer_separator - font-weight: bold !important - padding: 2px 8px 2px 0px !important - // }}} - // ListView {{{ - .oe-listview-content + width: auto + position: absolute + margin: 0 !important // dammit + padding: 0 + .oe_list_content .oe_group_header + @include vertical-gradient(#fcfcfc, #dedede) + + .oe_list_content width: 100% + td:first-child, th:first-child + &:after + border-width: 0 + td.oe_number + text-align: right !important + max-width: 100px > thead border-bottom: 2px solid #cacaca background: #eee vertical-align: top - td, th - padding: 3px 6px - line-height: 18px - th:after - content: "" - float: right - margin-top: 7px - border-width: 0 4px 4px - border-style: solid - border-color: #000 transparent - visibility: hidden - th.sortup:after - visibility: visible - @include opacity(0.6) - th.sortdown:after - border-bottom: none - border-left: 4px solid transparent - border-right: 4px solid transparent - border-top: 4px solid #000 - visibility: visible - @include box-shadow(none) - @include opacity(0.6) - // TODO mit - //th:hover:after - // border-bottom: none - // border-left: 4px solid transparent - // border-right: 4px solid transparent - // border-top: 4px solid #000 - // visibility: visible - // @include box-shadow(none) - // @include opacity(0.6) + th + position: relative + td, th + padding: 3px 6px + line-height: 18px + th:after + position: absolute + right: 6px + content: "" + margin-top: 7px + border-width: 0 4px 4px + border-style: solid + border-color: #000 transparent + visibility: hidden + th.sortup:after + visibility: visible + @include opacity(0.6) + th.sortdown:after + border-bottom: none + border-left: 4px solid transparent + border-right: 4px solid transparent + border-top: 4px solid #000 + visibility: visible + @include box-shadow(none) + @include opacity(0.6) > tbody cursor: pointer - background: white > tr + height: $row-height border-top: 1px solid #ddd - > td.oe-field-cell + > td.oe_list_field_cell padding: 3px 6px + white-space: pre-line > td, > th - line-height: 18px > button border: none background: transparent @@ -1789,46 +2129,93 @@ $colour4: #8a89ba width: 17px &:after border-width: 0 - > td.oe_number - text-align: right !important > tr:nth-child(odd) background-color: #f0f0fa @include vertical-gradient(#f0f0fa, #eeeef6) + > tfoot border-top: 2px solid #cacaca border-bottom: 1px solid #cacaca background: #eee font-weight: bold - > tbody tr:hover td, tbody tr:hover th - @include vertical-gradient(#eee, #dedede) .numeric text-align: right width: 82px input text-align: right + th.oe_list_header_handle + font-size: 1px + overflow: hidden + text-indent: -9001px + td.oe_list_field_handle + width: 1em + padding: 0 !important + cursor: ns-resize + .oe_list_handle + @include text-to-entypo-icon("}",#E0E0E0,18px) + margin-right: 7px + + .oe_list_cannot_edit + .oe_list_header_handle, .oe_list_field_handle + display: none !important + padding: 0 !important + .oe_list_cannot_delete + .oe_list_record_delete + display: none !important + // }}} - // Translation {{{ - .oe_trad_field.touched - border: 1px solid green !important - // }}} - // Tooltips {{{ - &.oe_tooltip - font-size: 12px - .oe_tooltip_string - color: #FD5 - font-weight: bold - font-size: 13px - .oe_tooltip_help - white-space: pre-wrap - .oe_tooltip_technical - padding: 0 0 4px 0 - margin: 5px 0 0 15px - li - list-style: circle - .oe_tooltip_technical_title + // Tree view {{{ + .tree_header + background-color: #f0f0f0 + border-bottom: 1px solid #cacaca + color: #4c4c4c + padding: 5px + height: 25px + button + float: right + height: 27px + margin-right: 5px + .oe-treeview-table + width: 100% + background-color: white + border-spacing: 0 + color: #4c4c4c + th + padding: 10px font-weight: bold + background-color: #f0f0f0 + border-bottom: 2px solid #cacaca + + td + cursor: pointer + vertical-align: middle + text-align: left + vertical-align: middle + height: 20px + padding-left: 4px + padding-right: 4px + border-right: 1px solid #e7e7e7 + + td.oe_number + text-align: right !important + tr + border-bottom: 1px solid #d6d6d6 + &:hover + background-color: #e7e7e7 + span + font-size: 90% + font-weight: normal + white-space: nowrap + display: block + .treeview-tr.oe-treeview-first + background: transparent url(/web/static/src/img/expand.gif) 0 50% no-repeat + .oe_open .treeview-tr.oe-treeview-first + background-image: url(/web/static/src/img/collapse.gif) + .treeview-tr.oe-treeview-first span, .treeview-td.oe-treeview-first span + margin-left: 16px + // }}} // Debugging stuff {{{ .oe_layout_debugging @@ -1847,7 +2234,6 @@ $colour4: #8a89ba float: right .oe_form_group_row_incomplete.oe_form_group_row_newline > td:last-child:after content: "[newline]" - .oe_debug_view float: left .oe_debug_view_log @@ -1865,26 +2251,7 @@ $colour4: #8a89ba float: right color: #333 // }}} -//view editor -.openerp - .oe_view_editor - width: 100% - border-collapse: collapse - margin-left: -12px - width: 100% - background-color: white - border-spacing: 0 - td - text-align: center - white-space: nowrap - border: 1px solid #D8D8D8 - cursor: pointer - font-size: 90% - .oe_view_editor_field td - border: 0px !important - .oe_view_editor tr:hover - background-color: #ecebf2 - +// Kitten Mode {{{ .kitten-mode-activated background-image: url(http://placekitten.com/g/1365/769) background-size: cover @@ -1892,16 +2259,39 @@ $colour4: #8a89ba >* opacity: 0.70 -// Transitional overrides for old styles {{{ -.openerp - .oe_form_field_many2one - td:first-child - position: relative - span.oe-m2o-drop-down-button - position: absolute - top: 3px - right: 2px // }}} +// The jQuery-ui overlay and Autocomplete are outside the .openerp div, please don't add indentation !!! +div.ui-widget-overlay + background: black + @include opacity(0.3) + +.ui-widget + font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif + color: #4c4c4c + font-size: 13px + +.ui-menu + padding: 2px 0 + @include box-shadow(0 1px 4px rgba(0,0,0,0.3)) + margin-top: 4px + border: 1px solid $tag-border + .ui-menu-item + width: 100% + padding: 0 + a + padding: 1px 16px + a.ui-corner-all + @include radius(0) + a.ui-state-active + background: #f0f0fa + .ui-menu-item a.ui-state-hover, .ui-menu-item a.ui-state-active + background: $tag-bg-dark + //color: #4c4c4c + //@include vertical-gradient(#f0f0fa, #eeeef6) + +.ui-corner-all + @include radius(3px) + // au BufWritePost,FileWritePost *.sass :!sass --style expanded --line-numbers <afile> > "%:p:r.css" // vim:tabstop=4:shiftwidth=4:softtabstop=4:fdm=marker: diff --git a/addons/web/static/src/css/base_old.css b/addons/web/static/src/css/base_old.css deleted file mode 100644 index 74b660f7613..00000000000 --- a/addons/web/static/src/css/base_old.css +++ /dev/null @@ -1,999 +0,0 @@ -.openerp { - padding: 0; - margin: 0; - font-size: 80%; - font-family: Ubuntu, Helvetica, sans-serif; -} - -.openerp, .openerp textarea, .openerp input, .openerp select, .openerp option, -.openerp button, .openerp .ui-widget { - font-family: Ubuntu, Helvetica, sans-serif; - font-size:85%; -} - -.openerp .view-manager-main-content { - width: 100%; -} - -.openerp .oe_form_group_cell .view-manager-main-content { - padding: 0; -} - -.oe_box { - border: 1px solid #aaf; - padding: 2px; - margin: 2px; -} - - -#oe_errors pre { - margin: 0; -} - -.openerp .oe-listview .oe-number { - text-align: right !important; -} -.oe-listview-header-columns { - background: #d1d1d1; /* Old browsers */ - background: -moz-linear-gradient(top, #ffffff 0%, #d1d1d1 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(100%,#d1d1d1)); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, #ffffff 0%,#d1d1d1 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(top, #ffffff 0%,#d1d1d1 100%); /* Opera11.10+ */ - background: -ms-linear-gradient(top, #ffffff 0%,#d1d1d1 100%); /* IE10+ */ - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#FFFFFF', endColorstr='#d1d1d1',GradientType=0 ); /* IE6-9 */ - background: linear-gradient(top, #ffffff 0%,#d1d1d1 100%); /* W3C */ -} - -.openerp .oe_hide { - display: none !important; -} - -/* STATES */ -.openerp .on_logged, -.openerp .db_options_row { - display: none; -} - - - -/* Database */ -.login .oe-database-manager { - display: none; - height: 100%; - width: 100%; - background-color: white; -} -.login.database_block .bottom, -.login.database_block .login_error_message, -.login.database_block .pane { - display: none; -} -.login.database_block .oe-database-manager { - display: block; -} - -.login .database { - float: left; - width: 202px; - height: 100%; - background: #666666; -} -.login .oe_db_options { - margin-left: 202px; - color: black; - padding-top: 20px; -} - -.login .database ul { - margin-top: 65px; -} - -ul.db_options li { - padding: 5px 0 10px 5px; - background: #949292; /* Old browsers */ - background: -moz-linear-gradient(top, #949292 30%, #6d6b6b 95%, #282828 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left top, left bottom, color-stop(30%,#949292), color-stop(95%,#6d6b6b), color-stop(100%,#282828)); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, #949292 30%,#6d6b6b 95%,#282828 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(top, #949292 30%,#6d6b6b 95%,#282828 100%); /* Opera11.10+ */ - background: -ms-linear-gradient(top, #949292 30%,#6d6b6b 95%,#282828 100%); /* IE10+ */ - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#949292', endColorstr='#282828',GradientType=0 ); /* IE6-9 */ - background: linear-gradient(top, #949292 30%,#6d6b6b 95%,#282828 100%); /* W3C */ - /* for ie9 */ - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#949292', endColorstr='#5B5A5A',GradientType=0 ); /* IE6-9 */ - border: none; - /* overriding jquery ui */ - -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; - display: block; - font-weight: bold; - text-transform: uppercase; - margin: 1px; - color: #EEEEEE; - cursor: pointer; - width: 195px; - font-size: 12px; -} - -.db_option_table { - border: 1px solid #5A5858; - padding: 5px; - -moz-border-radius: 10px; -} - -table.db_option_table input.required { - background-color: #D2D2FF !important; -} - -.db_option_table label { - display: block; - text-align: right; -} - -.db_option_table input[type="text"], -.db_option_table input[type="password"], -.db_option_table input[type="file"], -.db_option_table select { - width: 300px; -} - -.option_string { - font-weight: bold; - color: #555; - width: 100%; - text-align: center; - padding: 10px 0; - font-size: large; -} - -label.error { - float: none; - color: red; - padding-left: .5em; - vertical-align: top; -} - -/* Main*/ -.openerp .main_table { - width: 100%; - height: 100%; - background: #f0eeee; -} -.openerp .oe-application { - height: 100%; -} -.openerp .oe-application-container { - width: 100%; - height: 100%; -} - -/* IE Hack - for IE < 9 - * Avoids footer to be placed statically at 100% cutting the middle of the views - * */ -.openerp .oe-application-container { - height: auto\9; - min-height: 100%\9; -} - - -/* Main Application */ -.openerp .oe-main-content { - padding: 0; - height: 100%; -} - -.openerp h2.oe_view_title { - font-size: 110%; - font-weight: normal; - margin: 2px 0; - color: #252424; - text-shadow: white 0 1px 0; -} -.openerp div[id^="notebook"] .oe_view_title { - font-size:85%; - padding-bottom:4px; -} - -/* List */ -.openerp table.oe-listview-content { - clear: right; - width: 100%; - border-spacing: 0; - border: 1px solid silver; -} - -.openerp .oe-listview thead table { - width: 100%; - border: none; -} -.openerp .oe-listview tr.odd { - background-color: #f3f3f3; -} -.openerp .oe-listview tbody tr:hover { - background-color: #ecebf2; -} -.openerp .oe-listview tbody tr:hover { - background-color: #eae9f0; -} -.openerp .oe-listview thead table tr, -.openerp .oe-listview thead table tr:hover { - background: none; -} - -.openerp .oe-listview > table > tbody > tr > td, -.openerp .oe-listview th { - vertical-align: middle; - text-align: left; - padding: 1px 2px; -} - -.openerp .oe-record-delete button, -.openerp button.oe-edit-row-save { - border: none; - height: 12px; - width: 12px; - background: url("/web/static/src/img/iconset-b-remove.png") no-repeat scroll center center transparent; - cursor: pointer; -} -.openerp button.oe-edit-row-save { - background-image: url('/web/static/src/img/icons/save-document.png'); -} - -/* Could use :not selectors if they were supported by MSIE8... */ -.openerp .oe-listview > table > tbody > tr > td { - border-left: 1px solid #dadada; /*currently commenting to test with no vertical lines in list view*/ -} -.openerp .oe-listview tbody td:first-child, -.openerp .oe-listview tbody td.oe-button, -.openerp .oe-listview tbody td.oe-button, -.openerp .oe-listview tbody th.oe-record-selector, -.openerp .oe-listview tbody td.oe-record-delete { - border-left: none; -} - -.openerp .oe-listview td.oe-record-delete { - text-align: right; -} -.openerp .oe-listview th.oe-sortable { - cursor: pointer; - font-size: 75%; - text-transform: uppercase; - padding: 0; - margin: 0; - padding-left: 3px; - color: #333; -} -.openerp .oe-listview th.oe-sortable .ui-icon { - height: 60%; - margin: -6px 0 0; - display: inline; - display: inline-block; - vertical-align: middle; -} - -.openerp .oe-listview > table > tbody > tr > td { - border-bottom: 1px solid #E3E3E3; -} - - -.openerp .oe-listview td.oe-actions { - border-bottom:none; -} - -.openerp .oe-listview .oe-record-selector, .openerp .oe-listview .oe-record-edit-link { - border-bottom: 1px solid #E3E3E3; -} -.openerp .oe-listview .oe-record-edit-link { - cursor: pointer; -} - -.openerp .oe-listview .oe-field-cell { - cursor: pointer; - margin-top: 0; - margin-bottom: 0; - padding-top: 3px; - padding-bottom: 3px; - font-size: 80%; -} -.openerp .oe-listview .oe-field-cell progress { - width: 100%; -} -.openerp .oe-listview .oe-field-cell.oe-button button, -.openerp .oe-listview .oe_form_button button { - margin: 0; - padding: 0; - border: none; - background: none; - width: 16px; - box-shadow: none; - -moz-box-shadow: none; - -webkit-box-shadow: none; -} -.openerp .oe-listview .oe-field-cell button:active { - opacity: 0.5; -} -.openerp .oe-listview .oe-field-cell button img { - cursor: pointer; -} -.openerp .oe-listview .oe-field-cell button img:hover { - opacity: 0.75; -} - -.openerp .oe-listview .oe-field-cell .oe-listview-button-disabled img { - opacity: 0.5; -} - -.openerp .oe-listview th.oe-actions { - text-align: left; - white-space: nowrap; -} -.openerp .oe-listview th.oe-list-pager { - text-align: right; - white-space: nowrap; -} -.openerp .oe-list-pager .oe-pager-state { - cursor: pointer; - font-size: 90%; - color: #555; -} - -.openerp .oe_button.oe_button_pager, -.openerp .oe-list-pager > span, -.openerp .oe_form_pager > span { - line-height: 17px; - height: 17px; - cursor: pointer; - color: gray; - font-weight: bold; - vertical-align: middle; -} -.openerp .oe_button.oe_button_pager, -.openerp .oe_button.oe_button_pager:disabled { - padding: 0 3px 0 3px; - margin: 0; - height: 17px; -} -.openerp .oe-listview .oe-group-name { - padding-right: 1em; -} -.openerp .oe-listview .oe-group-name, -.openerp .oe-listview .oe-group-pagination { - white-space: nowrap; -} - -.openerp .oe-listview tfoot td { - padding: 3px 3px 0; -} -.openerp .oe-listview .oe-list-footer { - text-align: center; - white-space: nowrap; - color: #444; - font-size: 85%; -} -.openerp .oe-listview .oe-list-footer span { - margin: 0 1em; -} -.openerp .oe-listview .oe-list-footer progress { - vertical-align:-10% !important; - width: 100%; -} - -/** list rounded corners - - rounded corners are a pain on tables: need to round not only table, but - also on the first and last children of the first and last row - */ -.openerp .oe-listview table.oe-listview-content { - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; -} -.openerp .oe-listview table.oe-listview-content thead tr:first-child th:first-child { - -webkit-border-top-left-radius: 4px; - -moz-border-radius-topleft: 4px; - border-top-left-radius: 4px; -} -.openerp .oe-listview table.oe-listview-content thead tr:first-child th:last-child { - -webkit-border-top-right-radius: 4px; - -moz-border-radius-topright: 4px; - border-top-right-radius: 4px; -} -.openerp .oe-listview table.oe-listview-content tfoot tr:last-child th:first-child, -.openerp .oe-listview table.oe-listview-content tfoot tr:last-child td:first-child, -.openerp .oe-listview table.oe-listview-content tbody:last-child tr:last-child th:first-child { - -webkit-border-bottom-left-radius: 4px; - -moz-border-radius-bottomleft: 4px; - border-bottom-left-radius: 4px; -} -.openerp .oe-listview table.oe-listview-content tfoot tr:last-child th:last-child, -.openerp .oe-listview table.oe-listview-content tfoot tr:last-child td:last-child, -.openerp .oe-listview table.oe-listview-content tbody:last-child tr:last-child td:last-child { - -webkit-border-bottom-right-radius: 4px; - -moz-border-radius-bottomright: 4px; - border-bottom-right-radius: 4px; -} - -/* Form */ -.openerp .oe_form_group_cell input[type="checkbox"] { - margin-top: 3px; - vertical-align: middle; -} -.openerp .oe_form_group_cell .input[type="text"] { - padding-bottom: 1px; -} - -.openerp table.oe_form_group td { - color: #4c4c4c; -} -.openerp table.oe_form_group { - color: #4c4c4c; -} -.openerp fieldset.oe_group_box { - border: 1px solid #AAAAAA; - moz-border-radius: 4px; - -webkit-border-radius: 4px; - border-radius: 4px; - background: #F9F9F9; - padding: 4px; -} -.openerp fieldset.oe_group_box legend { - font-weight: bold; -} -.openerp .required.error { - border: 1px solid #900; -} -.openerp .oe_form_buttons, .openerp .oe_list_buttons { - float: left; -} -.openerp .oe_form_pager, .openerp .oe_list_pager { - float: right; - font-size: 80%; - color: gray; - font-weight: bold; -} - -.openerp .oe_form_pager { - margin-right: 3px; -} - -.openerp .oe_align_left { - text-align: left; -} -.openerp .oe_align_right { - text-align: right; -} -.openerp .oe_align_center { - text-align: center; -} -.openerp .oe_form .oe_form_paragraph { - margin: 3px 0 0 0; - white-space: normal; -} - -.openerp .oe_form .oe_form_paragraph.oe_multilines { - white-space: pre; -} - -.openerp .oe_form_field_one2many .oe-actions h3.oe_view_title, -.openerp .oe_form_field_one2many_list .oe-actions h3.oe_view_title{ - display: inline; - margin: 0 0.5em 0 0; -} - -.openerp .oe_form .oe-listview th.oe-sortable .ui-icon, -.openerp .oe_form .oe-listview th.oe-sortable .ui-icon { - height: 100%; - margin-top: -9px; -} - -.openerp table.oe_form_group .oe-listview-content td { - color: inherit; -} - -/* Uneditable Form View */ -.openerp .oe_form_readonly { - -} -.openerp .oe_form_readonly .oe_form_group_cell .field_text, -.openerp .oe_form_readonly .field_char, -.openerp .oe_form_readonly .field_int, -.openerp .oe_form_readonly .field_float, -.openerp .oe_form_readonly .field_email, -.openerp .oe_form_readonly .field_date, -.openerp .oe_form_readonly .field_selection, -.openerp .oe_form_readonly .oe_form_field_many2one { - padding: 3px 2px 2px 2px; - background-color: white; - height: 17px; -} -.openerp .oe_form_readonly .oe_form_group_cell .field_text { - height: auto; -} -.openerp .oe_form_readonly .field_datetime { - padding: 1px 2px 2px 2px; - background-color: white; - height:19px; -} -.openerp .oe_form_readonly .oe_form_field_many2one div { - background-color:white; - height:18px; - margin-bottom:1px; - padding: 0px 2px 5px 2px; -} - -.openerp .oe_form_readonly .oe_form_field_email div { - background-color: white; - padding: 1px 2px 3px 2px; -} - - -.openerp .oe_form_readonly .oe_form_field_text div.field_text, -.openerp .oe_form_readonly .oe_form_field_text_html div.field_text_html { - white-space: pre-wrap; -} -.openerp .oe_form_readonly .oe_form_group_cell .field_text { - min-height:100px; -} -/* Inputs */ -/* vertically recentering filter management select tag */ -.openerp select.oe_search-view-filters-management { - margin-top:2px; -} - - -@-moz-document url-prefix() { - /* Strange firefox behaviour on width: 100% + white-space: nowrap */ - .openerp .oe_form .oe_form_button .oe_button { - width: auto; - } -} -/* IE Hack - for IE < 9 - * Avoids buttons overflow - * */ -.openerp .oe_form .oe_form_button .oe_button { - min-width: auto\9; -} - - -/* jQuery UI override */ -.openerp .ui-widget { - font-size: 1em; -} -.openerp tbody.ui-widget-content { - margin-bottom: 10px; - border-spacing: 4px; -} -.openerp .ui-widget-header { - background: white none; -} - -/* Sidebar */ -.openerp .view-manager-main-table { - margin: 0; - width:100%; - border-collapse:collapse; - height:100%; -} - -.openerp .view-manager-main-table tbody { - vertical-align: top; -} - -.openerp .oe-view-manager-header { - overflow: auto; - background: url("/web/static/src/img/sep-a.gif") 0 100% repeat-x; - margin:6px 0 6px 2px; -} -.openerp .oe_form_group_cell .oe-view-manager-header { /* Trick: remove the background when element is in a formular */ - background: none; -} - -.openerp .oe-view-manager-header h2 { - float: left; -} - -.openerp .oe_view_manager_menu_tips blockquote { - display: none; - font-size: 85%; - margin: 0; - background: #fff; - border-bottom: 1px solid #CECBCB; - padding: 1px 10px; - color: #4C4C4C; -} -.openerp .oe_view_manager_menu_tips blockquote p { - margin: 0; - padding: 6px 1px 4px; -} - -.openerp .oe_view_manager_menu_tips blockquote div { - text-align: right; - margin-right:10px; -} - -.openerp .oe_view_manager_menu_tips blockquote div button { - border: none; - background: none; - padding: 0 4px; - margin: 0; - display: inline; - text-decoration: underline; - color: inherit; -} - -.openerp .view-manager-main-sidebar { - width: 180px; - padding: 0; - margin: 0; -} - -.openerp .sidebar-main-div { - height: 100%; - border-left: 1px solid #D2CFCF; -} - -.openerp .sidebar-content { - padding: 0; - margin: 0; - width: 180px; - height: 100%; - font-size: 0.9em; -} - -.openerp .closed-sidebar .sidebar-content { - width: 22px; -} - -.openerp .closed-sidebar .sidebar-content { - display: none; -} - -.openerp .sidebar-main-div a { - color: #555; - text-decoration: none; -} - -.openerp .sidebar-main-div a:hover { - color: black; -} - -.openerp .oe-sidebar-attachments-toolbar { - margin: 4px 0 0 4px; -} -.openerp .oe-sidebar-attachments-items { - clear: both; - padding-top: 5px !important; -} -.openerp .oe-sidebar-attachments-items li { - position: relative; - padding: 0 0 3px 10px !important; -} -.openerp .oe-sidebar-attachments-items li:hover { - background: #ddd; -} -.openerp .oe-sidebar-attachments-link { - display: block; - margin-right: 15px; - overflow: hidden; -} -.openerp .oe-sidebar-attachment-delete { - position: absolute; - right: 2px; - top: 1px; - overflow: hidden; - width: 15px; - height: 15px; - padding: 1px; - border-radius: 7px; - -moz-border-radius: 7px; - -webkit-border-radius: 7px; -} -.openerp .oe-sidebar-attachment-delete:hover { - background-color: white; -} - -.openerp .view-manager-main-sidebar h2 { - margin:0; - font-size: 1.15em; - color: #8E8E8E; - text-shadow: white 0 1px 0; - padding-left: 10px; - padding-right: 21px; - height: 21px; - - background: #ffffff; /* Old browsers */ - background: -moz-linear-gradient(top, #ffffff 0%, #ebe9e9 100%); /* FF3.6+ */ - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(100%,#ebe9e9)); /* Chrome,Safari4+ */ - background: -webkit-linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* Chrome10+,Safari5.1+ */ - background: -o-linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* Opera11.10+ */ - background: -ms-linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* IE10+ */ - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#FFFFFF', endColorstr='#EBE9E9',GradientType=0 ); /* IE6-9 */ - background: linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* W3C */ - - border: 1px solid #D2CFCF; - border-right-width: 0; - border-left-width: 0; -} -.openerp .view-manager-main-sidebar h2 { - border-top-width: 0; -} - -.openerp .view-manager-main-sidebar ul { - list-style-type: none; - margin: 0; - padding: 0; - display: block; -} - -.openerp .view-manager-main-sidebar li { - display: block; - padding: 3px 3px 3px 10px; -} - -.openerp .toggle-sidebar { - cursor: pointer; - border: 1px solid #D2CFCF; - border-top-width: 0; - display: block; - background: url(/web/static/src/img/toggle-a-bg.png); - width: 21px; - height: 21px; - z-index: 10; -} -.openerp .open-sidebar .toggle-sidebar { - margin-left: 158px; - background-position: 21px 0; - position: absolute; -} -.openerp .closed-sidebar .toggle-sidebar { - border-left: none; -} -.openerp li.oe_sidebar_print { - padding-left: 20px; - background: 1px 3px url(/web/static/src/img/icons/gtk-print.png) no-repeat; -} - -.openerp .oe_sidebar_print ul { - padding-left:8px; -} - -.openerp.kitten-mode-activated .main_table { - background: url(http://placekitten.com/g/1500/800) repeat; -} -.openerp.kitten-mode-activated.clark-gable .main_table { - background: url(http://amigrave.com/ClarkGable.jpg); - background-size: 100%; -} - -.openerp.kitten-mode-activated .header { - background: url(http://placekitten.com/g/211/65) repeat; -} - -.openerp.kitten-mode-activated .menu { - background: #828282; - background: -moz-linear-gradient(top, #828282 0%, #4D4D4D 100%); - background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#828282), color-stop(100%,#4D4D4D)); - filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#828282', endColorstr='#4D4D4D',GradientType=0 ); -} -.openerp.kitten-mode-activated .menu a { - background: none; -} -.openerp.kitten-mode-activated .menu span { - background: none; -} -.openerp.kitten-mode-activated .sidebar-content li a, -.openerp.kitten-mode-activated .oe-application .view-manager-main-content h2.oe_view_title, -.openerp.kitten-mode-activated .oe-application .view-manager-main-content a.searchview_group_string, -.openerp.kitten-mode-activated .oe-application .view-manager-main-content label { - color: white; -} -.openerp.kitten-mode-activated .menu, -.openerp.kitten-mode-activated .header_corner, -.openerp.kitten-mode-activated .header_title, -.openerp.kitten-mode-activated .oe-application, -.openerp.kitten-mode-activated .oe_footer, -.openerp.kitten-mode-activated .loading, -.openerp.kitten-mode-activated .ui-dialog { - opacity:0.8; - filter:alpha(opacity=80); -} -.openerp.kitten-mode-activated .header .company_logo { - background: url(http://placekitten.com/g/180/46); -} -.openerp.kitten-mode-activated .loading { - background: #828282; - border-color: #828282; -} - -.openerp .oe-m2o-drop-down-button { - margin-left: -24px; -} -.openerp .oe-m2o-drop-down-button img { - margin-bottom: -4px; - cursor: pointer; -} -.openerp .oe-m2o input { - border-right: none; - margin-right: 0px !important; - padding-bottom: 2px !important; -} -.openerp .oe-m2o-disabled-cm { - color: grey; -} -.openerp ul[role="listbox"] li a { - font-size:80%; -} -.parent_top { - vertical-align: text-top; -} - -.openerp .oe-dialog-warning p { - padding-left: 1em; - font-size: 1.2em; - font-weight: bold; -} - -.openerp .dhx_mini_calendar { - -moz-box-shadow: none; - -khtml-box-shadow: none; - -webkit-box-shadow: none; - box-shadow: none; -} -.openerp .oe-treeview-table { - width: 100%; - background-color : #FFFFFF; - border-spacing: 0; - -} -.openerp .oe-treeview-table tr:hover{ - color: blue; - background-color : #D8D8D8; -} -.treeview-tr, .treeview-td { - cursor: pointer; - vertical-align: top; - text-align: left; - border-bottom: 1px solid #CFCCCC; -} -.openerp .oe-treeview-table .oe-number { - text-align: right !important; -} -.treeview-tr span, .treeview-td span { - font-size: 90%; - font-weight: normal; - white-space: nowrap; - display: block; - } -.treeview-tr.oe-treeview-first { - background: transparent url(/web/static/src/img/expand.gif) 0 50% no-repeat; -} -.oe-open .treeview-tr.oe-treeview-first { - background-image: url(/web/static/src/img/collapse.gif); -} -.treeview-tr.oe-treeview-first span, -.treeview-td.oe-treeview-first span { - margin-left: 16px; -} - -.treeview-header { - vertical-align: top; - background-color : #D8D8D8; - white-space: nowrap; - text-align: left; - padding: 4px 5px; -} -ul.oe-arrow-list { - padding-left: 1.1em; - margin: 0; - white-space: nowrap; -} -ul.oe-arrow-list li { - display: inline-block; - margin-left: -1em; -} -ul.oe-arrow-list li span { - vertical-align: top; - display: inline-block; - border: 1em solid #DEDEDE; - line-height:0em; -} -ul.oe-arrow-list .oe-arrow-list-before { - border-left-color: rgba(0,0,0,0); - border-right-width:0; -} -ul.oe-arrow-list .oe-arrow-list-after { - border-color: rgba(0,0,0,0); - border-left-color: #DEDEDE; - border-right-width:0; -} -ul.oe-arrow-list li.oe-arrow-list-selected span { - border-color: #B5B9FF; -} -ul.oe-arrow-list li.oe-arrow-list-selected .oe-arrow-list-before { - border-left-color: rgba(0,0,0,0); -} -ul.oe-arrow-list li.oe-arrow-list-selected .oe-arrow-list-after { - border-color: rgba(0,0,0,0); - border-left-color: #B5B9FF; -} -.openerp ul.oe-arrow-list li:first-child span:first-child{ - -webkit-border-top-left-radius: 3px; - -moz-border-radius-topleft: 3px; - border-top-left-radius: 3px; - -webkit-border-bottom-left-radius: 3px; - -moz-border-radius-bottomleft: 3px; - border-bottom-left-radius: 3px; -} -.openerp ul.oe-arrow-list li:last-child span:last-child{ - -webkit-border-top-right-radius: 3px; - -moz-border-radius-topright: 3px; - border-top-right-radius: 3px; - -webkit-border-bottom-right-radius: 3px; - -moz-border-radius-bottomright: 3px; - border-bottom-right-radius: 3px; -} -.openerp .oe_view_editor { - width:100%; - border-collapse : collapse; - margin-left: -12px; - - width: 100%; - background-color : white; - border-spacing: 0; -} -.openerp .oe_view_editor td{ - text-align: center; - white-space: nowrap; - border: 1px solid #D8D8D8; - - cursor: pointer; - font-size: 90%; -} -.openerp .oe_view_editor_field td{ - border: 0px !important; -} - -.openerp .oe_view_editor tr:hover { - background-color: #ecebf2; -} - - -/* Dialog traceback cases */ -.openerp .oe_error_detail{ - display: block; -} -.openerp .oe_error_send{ - display:block; -} -.openerp .oe_fielddiv{ - display:inline-block; - width:100%; -} -.openerp .oe_fielddiv input[type=text],textarea{ - width:100%; -} -/* for Alignment center */ -.openerp .oe_centeralign{ - text-align:center; -} - -.openerp .oe_applications_tiles { - color: #4C4C4C; - text-shadow: #EEE 0 1px 0; - margin: 0 20px; -} - -.openerp select.oe_search-view-filters-management { - font-style: oblique; - color: #999999; -} - -.openerp .oe_search-view-filters-management option, -.openerp .oe_search-view-filters-management optgroup { - font-style: normal; - color: black; -} - - -/* Internet Explorer Fix */ -a img { - border: none; -} diff --git a/addons/web/static/src/css/data_export.css b/addons/web/static/src/css/data_export.css index 149fcea81e6..3fa24265b45 100644 --- a/addons/web/static/src/css/data_export.css +++ b/addons/web/static/src/css/data_export.css @@ -1,4 +1,4 @@ -.openerp .oe-export { +.openerp .oe_export { width: 100%; } .openerp .oe_export_row tr{ diff --git a/addons/web/static/src/css/data_import.css b/addons/web/static/src/css/data_import.css deleted file mode 100644 index a63a52802f0..00000000000 --- a/addons/web/static/src/css/data_import.css +++ /dev/null @@ -1,46 +0,0 @@ -.openerp .oe_import_grid { - border: none; - border-collapse: collapse; -} -.openerp .oe_import_grid-header .oe_import_grid-cell { - background: url(../img/gradientlinebg.gif) repeat-x #CCCCCC; - border-bottom: 1px solid #E3E3E3; - font-weight: bold; - text-align: left; -} -.openerp .oe_import_grid-row .oe_import_grid-cell { - border-bottom: 1px solid #E3E3E3; -} - -.openerp .oe-import-no-result .oe-import-result { - display: none; -} -.openerp .oe-import fieldset { - cursor: pointer; -} -.openerp .oe-import fieldset legend:before { - content: '\25BC '; -} -.openerp .oe-import fieldset.oe-closed legend:before { - content: '\25B6 '; -} -.openerp .oe-import fieldset.oe-closed table { - display: none; -} -.openerp .oe-import .separator.horizontal { - font-weight: bold; - border-bottom-width: 1px; - margin: 6px 4px 6px 1px; - height: 20px; -} -.openerp .duplicate_fld{ - background-color:#FF6666; -} -.openerp .select_fld{ - background: none repeat scroll 0 0 white; -} -.openerp .ui-autocomplete { - max-height: 300px; - overflow-y: auto; - padding-right: 20px; -} diff --git a/addons/web/static/src/img/graph_background.png b/addons/web/static/src/img/graph_background.png Binary files differnew file mode 100644 index 00000000000..530312b20bb --- /dev/null +++ b/addons/web/static/src/img/graph_background.png diff --git a/addons/web/static/src/img/pattern.png b/addons/web/static/src/img/pattern.png Binary files differdeleted file mode 100644 index 7e3a2b37834..00000000000 --- a/addons/web/static/src/img/pattern.png +++ /dev/null diff --git a/addons/web/static/src/img/throbber2.gif b/addons/web/static/src/img/throbber2.gif Binary files differdeleted file mode 100644 index 5d2057ae0e7..00000000000 --- a/addons/web/static/src/img/throbber2.gif +++ /dev/null diff --git a/addons/web/static/src/img/view_empty_arrow.png b/addons/web/static/src/img/view_empty_arrow.png Binary files differindex b0b1372adfd..ef8f3c820d7 100644 --- a/addons/web/static/src/img/view_empty_arrow.png +++ b/addons/web/static/src/img/view_empty_arrow.png diff --git a/addons/web/static/src/js/boot.js b/addons/web/static/src/js/boot.js index f83dbc97095..05ce709e80a 100644 --- a/addons/web/static/src/js/boot.js +++ b/addons/web/static/src/js/boot.js @@ -24,7 +24,7 @@ init: function(modules) { // By default only web will be loaded, the rest will be by loaded // by openerp.web.Session on the first session_authenticate - modules = modules || ["web"]; + modules = _.union(['web'], modules || []); var new_instance = { // links to the global openerp _openerp: openerp, @@ -37,7 +37,10 @@ }; openerp.instances[new_instance._session_id] = new_instance; for(var i=0; i < modules.length; i++) { - openerp[modules[i]](new_instance,new_instance[modules[i]]); + new_instance[modules[i]] = {}; + if (openerp[modules[i]]) { + openerp[modules[i]](new_instance,new_instance[modules[i]]); + } } return new_instance; } @@ -48,17 +51,12 @@ * OpenERP Web web module split *---------------------------------------------------------*/ openerp.web = function(session) { - var files = ["corelib","coresetup","dates","formats","chrome","data","views","search","list","form","list_editable","web_mobile","view_tree","data_export","data_import","view_editor"]; + var files = ["corelib","coresetup","dates","formats","chrome","data","views","search","list","form","list_editable","web_mobile","view_tree","data_export","data_import"]; for(var i=0; i<files.length; i++) { if(openerp.web[files[i]]) { openerp.web[files[i]](session); } } - session.log = function() { - if (session.connection.debug && window.console) { - console.log.apply(console, arguments); - } - } }; // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index a36421f2b2b..014313d5428 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -9,12 +9,11 @@ instance.web.Notification = instance.web.Widget.extend({ template: 'Notification', init: function() { this._super.apply(this, arguments); - // move to instance.web.notification - instance.notification = this; + instance.web.notification = this; }, start: function() { this._super.apply(this, arguments); - this.$element.notify({ + this.$el.notify({ speed: 500, expires: 2500 }); @@ -25,7 +24,7 @@ instance.web.Notification = instance.web.Widget.extend({ if (sticky) { opts.expires = false; } - this.$element.notify('create', { + this.$el.notify('create', { title: title, text: text }, opts); @@ -36,7 +35,7 @@ instance.web.Notification = instance.web.Widget.extend({ if (sticky) { opts.expires = false; } - this.$element.notify('create', 'oe_notification_alert', { + this.$el.notify('create', 'oe_notification_alert', { title: title, text: text }, opts); @@ -58,20 +57,18 @@ instance.web.Dialog = instance.web.Widget.extend({ init: function (parent, options, content) { var self = this; this._super(parent); - if (content) { - this.$element = content instanceof $ ? content : $(content); - } + this.content_to_set = content; this.dialog_options = { modal: true, destroy_on_close: true, - width: $(window).width() * (($(window).width() > 1024) ? 0.5 : 0.75), + width: 900, min_width: 0, max_width: '95%', height: 'auto', min_height: 0, - max_height: this.get_height('100%') - 140, + max_height: this.get_height('100%') - 200, autoOpen: false, - position: [false, 50], + position: [false, 40], buttons: {}, beforeClose: function () { self.on_close(); }, resizeStop: this.on_resized @@ -82,13 +79,11 @@ instance.web.Dialog = instance.web.Widget.extend({ } } if (options) { + if (options.buttons) { + this.params_buttons = true; + } _.extend(this.dialog_options, options); } - if (this.dialog_options.autoOpen) { - this.open(); - } else { - instance.web.dialog(this.$element, this.get_options()); - } }, get_options: function(options) { var self = this, @@ -120,32 +115,67 @@ instance.web.Dialog = instance.web.Widget.extend({ return parseInt(val, 10); } }, - open: function(options) { - // TODO fme: bind window on resize - if (this.template) { - this.$element.html(this.renderElement()); + renderElement: function() { + if (this.content_to_set) { + this.setElement(this.content_to_set); + } else if (this.template) { + this._super(); } + }, + open: function(options) { + if (! this.dialog_inited) + this.init_dialog(); var o = this.get_options(options); - instance.web.dialog(this.$element, o).dialog('open'); + if (! this.params_buttons) { + this.$buttons.appendTo($("body")); + } + instance.web.dialog(this.$el, o).dialog('open'); + if (! this.params_buttons) { + this.$buttons.appendTo(this.$el.dialog("widget")); + } if (o.height === 'auto' && o.max_height) { - this.$element.css({ 'max-height': o.max_height, 'overflow-y': 'auto' }); + this.$el.css({ 'max-height': o.max_height, 'overflow-y': 'auto' }); } return this; }, + init_dialog: function(options) { + this.renderElement(); + var o = this.get_options(options); + instance.web.dialog(this.$el, o); + if (! this.params_buttons) { + this.$buttons = $('<div class="ui-dialog-buttonpane ui-widget-content ui-helper-clearfix" />'); + this.$el.dialog("widget").append(this.$buttons); + } + var res = this.start(); + this.dialog_inited = true; + return res; + }, close: function() { - this.$element.dialog('close'); + this.$el.dialog('close'); }, on_close: function() { + if (this.__tmp_dialog_destroying) + return; if (this.dialog_options.destroy_on_close) { - this.$element.dialog('destroy'); + this.__tmp_dialog_closing = true; + this.destroy(); + this.__tmp_dialog_closing = undefined; } }, on_resized: function() { - //openerp.log("Dialog resized to %d x %d", this.$element.width(), this.$element.height()); }, destroy: function () { - this.close(); - this.$element.dialog('destroy'); + _.each(this.getChildren(), function(el) { + el.destroy(); + }); + if (! this.__tmp_dialog_closing) { + this.__tmp_dialog_destroying = true; + this.close(); + this.__tmp_dialog_destroying = undefined; + } + if (! this.isDestroyed()) { + this.$el.dialog('destroy'); + } this._super(); } }); @@ -176,29 +206,9 @@ instance.web.CrashManager = instance.web.CallbackEnabled.extend({ on_traceback: function(error) { var self = this; var buttons = {}; - if (instance.connection.openerp_entreprise) { - buttons[_t("Send OpenERP Enterprise Report")] = function() { - var $this = $(this); - var issuename = $('#issuename').val(); - var explanation = $('#explanation').val(); - var remark = $('#remark').val(); - // Call the send method from server to send mail with details - new instance.web.DataSet(self, 'publisher_warranty.contract').call_and_eval('send', [error.data,explanation,remark,issuename]).then(function(result){ - if (result === false) { - alert('There was a communication error.'); - } else { - $this.dialog('close'); - } - }); - }; - buttons[_t("Dont send")] = function() { - $(this).dialog("close"); - }; - } else { - buttons[_t("Ok")] = function() { - $(this).dialog("close"); - }; - } + buttons[_t("Ok")] = function() { + $(this).dialog("close"); + }; var dialog = new instance.web.Dialog(this, { title: "OpenERP " + _.str.capitalize(error.type), width: '80%', @@ -207,8 +217,15 @@ instance.web.CrashManager = instance.web.CallbackEnabled.extend({ min_height: '600px', buttons: buttons }).open(); - dialog.$element.html(QWeb.render('CrashManager.error', {session: instance.connection, error: error})); - } + dialog.$el.html(QWeb.render('CrashManager.error', {session: instance.session, error: error})); + }, + on_javascript_exception: function(exception) { + this.on_traceback({ + type: _t("Client Error"), + message: exception, + data: {debug: ""} + }); + }, }); instance.web.Loading = instance.web.Widget.extend({ @@ -217,47 +234,49 @@ instance.web.Loading = instance.web.Widget.extend({ this._super(parent); this.count = 0; this.blocked_ui = false; - var self = this; - this.request_call = function() { - self.on_rpc_event(1); - }; - this.response_call = function() { - self.on_rpc_event(-1); - }; - this.session.on_rpc_request.add_first(this.request_call); - this.session.on_rpc_response.add_last(this.response_call); + this.session.on("request", this, this.request_call); + this.session.on("response", this, this.response_call); + this.session.on("error", this, this.response_call); }, destroy: function() { - this.session.on_rpc_request.remove(this.request_call); - this.session.on_rpc_response.remove(this.response_call); this.on_rpc_event(-this.count); this._super(); }, + request_call: function() { + this.on_rpc_event(1); + }, + response_call: function() { + this.on_rpc_event(-1); + }, on_rpc_event : function(increment) { var self = this; if (!this.count && increment === 1) { // Block UI after 3s this.long_running_timer = setTimeout(function () { self.blocked_ui = true; - $.blockUI(); + instance.web.blockUI(); }, 3000); } this.count += increment; if (this.count > 0) { - this.$element.text(_.str.sprintf( _t("Loading (%d)"), this.count)); - this.$element.show(); - this.getParent().$element.addClass('oe_wait'); + if (instance.session.debug) { + this.$el.text(_.str.sprintf( _t("Loading (%d)"), this.count)); + } else { + this.$el.text(_t("Loading")); + } + this.$el.show(); + this.getParent().$el.addClass('oe_wait'); } else { this.count = 0; clearTimeout(this.long_running_timer); // Don't unblock if blocked by somebody else if (self.blocked_ui) { this.blocked_ui = false; - $.unblockUI(); + instance.web.unblockUI(); } - this.$element.fadeOut(); - this.getParent().$element.removeClass('oe_wait'); + this.$el.fadeOut(); + this.getParent().$el.removeClass('oe_wait'); } } }); @@ -265,13 +284,14 @@ instance.web.Loading = instance.web.Widget.extend({ instance.web.DatabaseManager = instance.web.Widget.extend({ init: function(parent) { this._super(parent); - this.unblockUIFunction = $.unblockUI; + this.unblockUIFunction = instance.web.unblockUI; $.validator.addMethod('matches', function (s, _, re) { return new RegExp(re).test(s); }, _t("Invalid database name")); }, start: function() { var self = this; + $('.oe_secondary_menus_container,.oe_user_menu_placeholder').empty(); var fetch_db = this.rpc("/web/database/get_list", {}).pipe( function(result) { self.db_list = result.db_list; @@ -287,17 +307,27 @@ instance.web.DatabaseManager = instance.web.Widget.extend({ }, do_render: function() { var self = this; - self.$element.html(QWeb.render("DatabaseManager", { widget : self })); - self.$element.find(".oe_database_manager_menu").tabs({ - show: function(event, ui) { - $('*[autofocus]:first', ui.panel).focus(); - } + instance.webclient.toggle_bars(true); + self.$el.html(QWeb.render("DatabaseManager", { widget : self })); + $('.oe_user_menu_placeholder').append(QWeb.render("DatabaseManager.user_menu",{ widget : self })); + $('.oe_secondary_menus_container').append(QWeb.render("DatabaseManager.menu",{ widget : self })); + $('ul.oe_secondary_submenu > li:first').addClass('oe_active') + $('ul.oe_secondary_submenu > li').bind('click', function (event) { + var menuitem = $(this); + menuitem.addClass('oe_active').siblings().removeClass('oe_active'); + var form_id =menuitem.find('a').attr('href'); + $(form_id).show().siblings().hide(); + event.preventDefault(); }); - self.$element.find("form[name=create_db_form]").validate({ submitHandler: self.do_create }); - self.$element.find("form[name=drop_db_form]").validate({ submitHandler: self.do_drop }); - self.$element.find("form[name=backup_db_form]").validate({ submitHandler: self.do_backup }); - self.$element.find("form[name=restore_db_form]").validate({ submitHandler: self.do_restore }); - self.$element.find("form[name=change_pwd_form]").validate({ + $('#back-to-login').click(self.do_exit); + self.$el.find("td").addClass("oe_form_group_cell"); + self.$el.find("tr td:first-child").addClass("oe_form_group_cell_label"); + self.$el.find("label").addClass("oe_form_label"); + self.$el.find("form[name=create_db_form]").validate({ submitHandler: self.do_create }); + self.$el.find("form[name=drop_db_form]").validate({ submitHandler: self.do_drop }); + self.$el.find("form[name=backup_db_form]").validate({ submitHandler: self.do_backup }); + self.$el.find("form[name=restore_db_form]").validate({ submitHandler: self.do_restore }); + self.$el.find("form[name=change_pwd_form]").validate({ messages: { old_pwd: "Please enter your previous password", new_pwd: "Please enter your new password", @@ -308,10 +338,9 @@ instance.web.DatabaseManager = instance.web.Widget.extend({ }, submitHandler: self.do_change_password }); - self.$element.find("#back_to_login").click(self.do_exit); }, destroy: function () { - this.$element.find('#db-create, #db-drop, #db-backup, #db-restore, #db-change-password, #back-to-login').unbind('click').end().empty(); + this.$el.find('#db-create, #db-drop, #db-backup, #db-restore, #db-change-password, #back-to-login').unbind('click').end().empty(); this._super(); }, /** @@ -332,16 +361,16 @@ instance.web.DatabaseManager = instance.web.Widget.extend({ * from unblocking the UI */ blockUI: function () { - $.blockUI(); - $.unblockUI = function () {}; + instance.web.blockUI(); + instance.web.unblockUI = function () {}; }, /** * Reinstates $.unblockUI so third parties can play with blockUI, and * unblocks the UI */ unblockUI: function () { - $.unblockUI = this.unblockUIFunction; - $.unblockUI(); + instance.web.unblockUI = this.unblockUIFunction; + instance.web.unblockUI(); }, /** * Displays an error dialog resulting from the various RPC communications @@ -365,10 +394,20 @@ instance.web.DatabaseManager = instance.web.Widget.extend({ var fields = $(form).serializeArray(); self.rpc("/web/database/create", {'fields': fields}, function(result) { var form_obj = self.to_object(fields); - self.getParent().do_login( form_obj['db_name'], 'admin', form_obj['create_admin_pwd']); - self.destroy(); + var client_action = { + type: 'ir.actions.client', + tag: 'login', + params: { + 'db': form_obj['db_name'], + 'login': 'admin', + 'password': form_obj['create_admin_pwd'], + 'login_successful': function() { + self.do_action("reload"); + }, + }, + }; + self.do_action(client_action); }); - }, do_drop: function(form) { var self = this; @@ -396,7 +435,14 @@ instance.web.DatabaseManager = instance.web.Widget.extend({ success: function () { self.do_notify(_t("Backed"), _t("Database backed up successfully")); }, - error: instance.webclient.crashmanager.on_rpc_error, + error: function(error){ + if(error){ + self.display_error({ + title: 'Backup Database', + error: 'AccessDenied' + }); + } + }, complete: function() { self.unblockUI(); } @@ -440,23 +486,33 @@ instance.web.DatabaseManager = instance.web.Widget.extend({ self.display_error(result); return; } + self.unblockUI(); self.do_notify("Changed Password", "Password has been changed successfully"); }); }, do_exit: function () { + this.$el.remove(); + instance.webclient.toggle_bars(false); + this.do_action('login'); } }); +instance.web.client_actions.add("database_manager", "instance.web.DatabaseManager"); instance.web.Login = instance.web.Widget.extend({ template: "Login", remember_credentials: true, - _db_list: null, - init: function(parent) { + init: function(parent, params) { this._super(parent); this.has_local_storage = typeof(localStorage) != 'undefined'; + this.db_list = null; this.selected_db = null; this.selected_login = null; + this.params = params || {}; + + if (this.params.login_successful) { + this.on('login_successful', this, this.params.login_successful); + } if (this.has_local_storage && this.remember_credentials) { this.selected_db = localStorage.getItem('last_db_login_success'); @@ -468,58 +524,47 @@ instance.web.Login = instance.web.Widget.extend({ }, start: function() { var self = this; - - self.$element.find("form").submit(self.on_submit); - - self.$element.find('.oe_login_manage_db').click(function() { - self.$element.find('.oe_login_bottom').hide(); - self.$element.find('.oe_login_pane').hide(); - self.databasemanager = new instance.web.DatabaseManager(self); - self.databasemanager.appendTo(self.$element); - self.databasemanager.do_exit.add_last(function() { - self.databasemanager.destroy(); - self.$element.find('.oe_login_bottom').show(); - self.$element.find('.oe_login_pane').show(); - self.load_db_list(true).then(self.proxy('_db_list_loaded')); - }); + self.$el.find("form").submit(self.on_submit); + self.$el.find('.oe_login_manage_db').click(function() { + self.do_action("database_manager"); }); - return self.load_db_list().then(self.proxy('_db_list_loaded')); - }, - load_db_list: function (force) { - var d = $.when(), self = this; - if (_.isNull(this._db_list) || force) { - d = self.rpc("/web/database/get_list", {}, function(result) { - self._db_list = _.clone(result.db_list); - }, function(error, event) { - if (error.data.fault_code === 'AccessDenied') { - event.preventDefault(); - } - }); + var d; + if (self.params.db) { + if (self.params.login && self.params.password) { + d = self.do_login(self.params.db, self.params.login, self.params.password); + } + } else { + d = self.rpc("/web/database/get_list", {}).done(self.on_db_loaded).fail(self.on_db_failed); } return d; }, - _db_list_loaded: function () { - var list = this._db_list, - dbdiv = this.$element.find('div.oe_login_dbpane'); - this.$element.find("[name=db]").replaceWith(instance.web.qweb.render('Login.dblist', { db_list: list, selected_db: this.selected_db})); - if(list && list.length === 1) { - dbdiv.hide(); + on_db_loaded: function (result) { + this.db_list = result.db_list; + this.$("[name=db]").replaceWith(QWeb.render('Login.dblist', { db_list: this.db_list, selected_db: this.selected_db})); + if(this.db_list.length === 0) { + this.do_action("database_manager"); + } else if(this.db_list.length === 1) { + this.$('div.oe_login_dbpane').hide(); } else { - dbdiv.show(); + this.$('div.oe_login_dbpane').show(); + } + }, + on_db_failed: function (error, event) { + if (error.data.fault_code === 'AccessDenied') { + event.preventDefault(); } }, on_submit: function(ev) { if(ev) { ev.preventDefault(); } - var $e = this.$element; - var db = $e.find("form [name=db]").val(); + var db = this.$("form [name=db]").val(); if (!db) { this.do_warn("Login", "No database selected !"); return false; } - var login = $e.find("form input[name=login]").val(); - var password = $e.find("form input[name=password]").val(); + var login = this.$("form input[name=login]").val(); + var password = this.$("form input[name=password]").val(); this.do_login(db, login, password); }, @@ -532,7 +577,8 @@ instance.web.Login = instance.web.Widget.extend({ */ do_login: function (db, login, password) { var self = this; - this.$element.removeClass('oe_login_invalid'); + self.hide_error(); + self.$(".oe_login_pane").fadeOut("slow"); return this.session.session_authenticate(db, login, password).pipe(function() { if (self.has_local_storage) { if(self.remember_credentials) { @@ -547,13 +593,102 @@ instance.web.Login = instance.web.Widget.extend({ localStorage.setItem('last_password_login_success', ''); } } - self.$(".oe_login_pane").fadeOut("slow"); - self.trigger("login"); - },function () { - self.$element.addClass("oe_login_invalid"); + self.trigger('login_successful'); + }, function () { + self.$(".oe_login_pane").fadeIn("fast", function() { + self.show_error("Invalid username or password"); + }); }); + }, + show_error: function(message) { + this.$el.addClass("oe_login_invalid"); + this.$(".oe_login_error_message").text(message); + }, + hide_error: function() { + this.$el.removeClass('oe_login_invalid'); + }, +}); +instance.web.client_actions.add("login", "instance.web.Login"); + +/** + * Client action to reload the whole interface. + * If params has an entry 'menu_id', it opens the given menu entry. + */ +instance.web.Reload = instance.web.Widget.extend({ + init: function(parent, params) { + this._super(parent); + this.menu_id = (params && params.menu_id) || false; + }, + start: function() { + var l = window.location; + + var sobj = $.deparam(l.search.substr(1)); + sobj.ts = new Date().getTime(); + var search = '?' + $.param(sobj); + + var hash = l.hash; + if (this.menu_id) { + hash = "#menu_id=" + this.menu_id; + } + var url = l.protocol + "//" + l.host + l.pathname + search + hash; + window.location = url; } }); +instance.web.client_actions.add("reload", "instance.web.Reload"); + +/** + * Client action to go back in breadcrumb history. + * If can't go back in history stack, will go back to home. + */ +instance.web.HistoryBack = instance.web.Widget.extend({ + init: function(parent, params) { + if (!parent.history_back()) { + window.location = '/' + (window.location.search || ''); + } + } +}); +instance.web.client_actions.add("history_back", "instance.web.HistoryBack"); + +/** + * Client action to go back home. + */ +instance.web.Home = instance.web.Widget.extend({ + init: function(parent, params) { + window.location = '/' + (window.location.search || ''); + } +}); +instance.web.client_actions.add("home", "instance.web.Home"); + +instance.web.ChangePassword = instance.web.Widget.extend({ + template: "ChangePassword", + start: function() { + var self = this; + self.$el.validate({ + submitHandler: function (form) { + self.rpc("/web/session/change_password",{ + 'fields': $(form).serializeArray() + }, function(result) { + if (result.error) { + self.display_error(result); + return; + } else { + instance.webclient.on_logout(); + } + }); + } + }); + }, + display_error: function (error) { + return instance.web.dialog($('<div>'), { + modal: true, + title: error.title, + buttons: [ + {text: _t("Ok"), click: function() { $(this).dialog("close"); }} + ] + }).html(error.error); + }, +}) +instance.web.client_actions.add("change_password", "instance.web.ChangePassword"); instance.web.Menu = instance.web.Widget.extend({ template: 'Menu', @@ -565,7 +700,8 @@ instance.web.Menu = instance.web.Widget.extend({ }, start: function() { this._super.apply(this, arguments); - this.$secondary_menus = this.getParent().$element.find('.oe_secondary_menus_container'); + this.$secondary_menus = this.getParent().$el.find('.oe_secondary_menus_container'); + this.$secondary_menus.on('click', 'a[data-menu]', this.on_menu_click); return this.do_reload(); }, do_reload: function() { @@ -576,13 +712,13 @@ instance.web.Menu = instance.web.Widget.extend({ this.data = data; this.renderElement(); this.limit_entries(); + // Hide toplevel item if there is only one + var $toplevel = this.$("li") + if($toplevel.length == 1) { + $toplevel.hide(); + } this.$secondary_menus.html(QWeb.render("Menu.secondary", { widget : this })); - this.$element.on('click', 'a.oe_menu_more_link', function() { - self.$element.find('.oe_menu_more').toggle(); - return false; - }); - this.$element.on('click', 'a[data-menu]', this.on_menu_click); - this.$secondary_menus.on('click', 'a[data-menu]', this.on_menu_click); + this.$el.on('click', 'a[data-menu]', this.on_menu_click); // Hide second level submenus this.$secondary_menus.find('.oe_menu_toggler').siblings('.oe_secondary_submenu').hide(); if (self.current_menu) { @@ -597,20 +733,17 @@ instance.web.Menu = instance.web.Widget.extend({ } if (maximum_visible_links < this.data.data.children.length) { var $more = $(QWeb.render('Menu.more')), - $index = this.$element.find('li').eq(maximum_visible_links - 1); + $index = this.$el.find('li').eq(maximum_visible_links - 1); $index.after($more); + //$('.oe_topbar').append($more); $more.find('.oe_menu_more').append($index.next().nextAll()); } - this.do_hide_more(); }, auto_limit_entries: function() { // TODO: auto detect overflow and bind window on resize var width = $(window).width(); return Math.floor(width / 125); }, - do_hide_more: function() { - this.$element.find('.oe_menu_more').hide(); - }, /** * Opens a given menu by id, as if a user had browsed to that menu by hand * except does not trigger any event on the way @@ -618,20 +751,22 @@ instance.web.Menu = instance.web.Widget.extend({ * @param {Number} id database id of the terminal menu to select */ open_menu: function (id) { + this.current_menu = id; + this.session.active_id = id; var $clicked_menu, $sub_menu, $main_menu; - $clicked_menu = this.$element.add(this.$secondary_menus).find('a[data-menu=' + id + ']'); + $clicked_menu = this.$el.add(this.$secondary_menus).find('a[data-menu=' + id + ']'); this.trigger('open_menu', id, $clicked_menu); if (this.$secondary_menus.has($clicked_menu).length) { $sub_menu = $clicked_menu.parents('.oe_secondary_menu'); - $main_menu = this.$element.find('a[data-menu=' + $sub_menu.data('menu-parent') + ']'); + $main_menu = this.$el.find('a[data-menu=' + $sub_menu.data('menu-parent') + ']'); } else { $sub_menu = this.$secondary_menus.find('.oe_secondary_menu[data-menu-parent=' + $clicked_menu.attr('data-menu') + ']'); $main_menu = $clicked_menu; } // Activate current main menu - this.$element.find('.oe_active').removeClass('oe_active'); + this.$el.find('.oe_active').removeClass('oe_active'); $main_menu.addClass('oe_active'); // Show current sub menu @@ -658,7 +793,7 @@ instance.web.Menu = instance.web.Widget.extend({ * @param {Number} id the action_id to match */ open_action: function (id) { - var $menu = this.$element.add(this.$secondary_menus).find('a[data-action-id=' + id + ']'); + var $menu = this.$el.add(this.$secondary_menus).find('a[data-action-id="' + id + '"]'); var menu_id = $menu.data('menu'); if (menu_id) { this.open_menu(menu_id); @@ -668,34 +803,37 @@ instance.web.Menu = instance.web.Widget.extend({ * Process a click on a menu item * * @param {Number} id the menu_id + * @param {Boolean} [needaction=false] whether the triggered action should execute in a `needs action` context */ - menu_click: function(id) { - if (id) { - this.do_hide_more(); - // find back the menuitem in dom to get the action - var $item = this.$element.find('a[data-menu=' + id + ']'); - if (!$item.length) { - $item = this.$secondary_menus.find('a[data-menu=' + id + ']'); - } - var action_id = $item.data('action-id'); - // If first level menu doesnt have action trigger first leaf - if (!action_id) { - if(this.$element.has($item).length) { - $sub_menu = this.$secondary_menus.find('.oe_secondary_menu[data-menu-parent=' + id + ']'); - $items = $sub_menu.find('a[data-action-id]').filter('[data-action-id!=""]'); - if($items.length) { - action_id = $items.data('action-id'); - id = $items.data('menu'); - } + menu_click: function(id, needaction) { + if (!id) { return; } + + // find back the menuitem in dom to get the action + var $item = this.$el.find('a[data-menu=' + id + ']'); + if (!$item.length) { + $item = this.$secondary_menus.find('a[data-menu=' + id + ']'); + } + var action_id = $item.data('action-id'); + // If first level menu doesnt have action trigger first leaf + if (!action_id) { + if(this.$el.has($item).length) { + var $sub_menu = this.$secondary_menus.find('.oe_secondary_menu[data-menu-parent=' + id + ']'); + var $items = $sub_menu.find('a[data-action-id]').filter('[data-action-id!=""]'); + if($items.length) { + action_id = $items.data('action-id'); + id = $items.data('menu'); } } - this.open_menu(id); - this.current_menu = id; - this.session.active_id = id; - if (action_id) { - this.trigger('menu_click', action_id, id, $item); - } } + if (action_id) { + this.trigger('menu_click', { + action_id: action_id, + needaction: needaction, + id: id, + previous_menu_id: this.current_menu // Here we don't know if action will fail (in which case we have to revert menu) + }, $item); + } + this.open_menu(id); }, /** * Jquery event handler for menu click @@ -703,9 +841,9 @@ instance.web.Menu = instance.web.Widget.extend({ * @param {Event} ev the jquery event */ on_menu_click: function(ev) { - this.menu_click($(ev.currentTarget).data('menu')); - ev.stopPropagation(); - return false; + ev.preventDefault(); + var needaction = $(ev.target).is('div.oe_menu_counter'); + this.menu_click($(ev.currentTarget).data('menu'), needaction); }, }); @@ -718,69 +856,30 @@ instance.web.UserMenu = instance.web.Widget.extend({ start: function() { var self = this; this._super.apply(this, arguments); - $('html').bind('click', function() { - self.$element.find('.oe_dropdown_options').hide(); - }); - this.$element.find('.oe_dropdown_toggle').click(function() { - self.$element.find('.oe_dropdown_options').toggle(); - return false; - }); - this.$element.on('click', '.oe_dropdown_options li a[data-menu]', function() { + this.$el.on('click', '.oe_dropdown_menu li a[data-menu]', function(ev) { + ev.preventDefault(); var f = self['on_menu_' + $(this).data('menu')]; if (f) { f($(this)); } - self.$element.find('.oe_dropdown_options').hide(); - return false; }); }, - change_password :function() { - var self = this; - this.dialog = new instance.web.Dialog(this, { - title: _t("Change Password"), - width : 'auto' - }).open(); - this.dialog.$element.html(QWeb.render("UserMenu.password", self)); - this.dialog.$element.find("form[name=change_password_form]").validate({ - submitHandler: function (form) { - self.rpc("/web/session/change_password",{ - 'fields': $(form).serializeArray() - }, function(result) { - if (result.error) { - self.display_error(result); - return; - } else { - instance.webclient.on_logout(); - } - }); - } - }); - }, - display_error: function (error) { - return instance.web.dialog($('<div>'), { - modal: true, - title: error.title, - buttons: [ - {text: _("Ok"), click: function() { $(this).dialog("close"); }} - ] - }).html(error.error); - }, do_update: function () { var self = this; var fct = function() { - var $avatar = self.$element.find('.oe_topbar_avatar'); + var $avatar = self.$el.find('.oe_topbar_avatar'); $avatar.attr('src', $avatar.data('default-src')); if (!self.session.uid) return; var func = new instance.web.Model("res.users").get_func("read"); return func(self.session.uid, ["name", "company_id"]).pipe(function(res) { var topbar_name = res.name; - if(instance.connection.debug) - topbar_name = _.str.sprintf("%s (%s)", topbar_name, instance.connection.db); + if(instance.session.debug) + topbar_name = _.str.sprintf("%s (%s)", topbar_name, instance.session.db); if(res.company_id[0] > 1) topbar_name = _.str.sprintf("%s (%s)", topbar_name, res.company_id[1]); - self.$element.find('.oe_topbar_name').text(topbar_name); - var avatar_src = _.str.sprintf('%s/web/binary/image?session_id=%s&model=res.users&field=avatar&id=%s', self.session.prefix, self.session.session_id, self.session.uid); + self.$el.find('.oe_topbar_name').text(topbar_name); + var avatar_src = _.str.sprintf('%s/web/binary/image?session_id=%s&model=res.users&field=image_small&id=%s', self.session.prefix, self.session.session_id, self.session.uid); $avatar.attr('src', avatar_src); }); }; @@ -792,44 +891,12 @@ instance.web.UserMenu = instance.web.Widget.extend({ }, on_menu_settings: function() { var self = this; - var action_manager = new instance.web.ActionManager(this); - var dataset = new instance.web.DataSet (this,'res.users',this.context); - dataset.call ('action_get','',function (result){ - self.rpc('/web/action/load', {action_id:result}, function(result){ - action_manager.do_action(_.extend(result['result'], { - target: 'inline', - res_id: self.session.uid, - res_model: 'res.users', - flags: { - action_buttons: false, - search_view: false, - sidebar: false, - views_switcher: false, - pager: false - } - })); + if (!this.getParent().has_uncommitted_changes()) { + self.rpc("/web/action/load", { action_id: "base.action_res_users_my" }, function(result) { + result.result.res_id = instance.session.uid; + self.getParent().action_manager.do_action(result.result); }); - }); - this.dialog = new instance.web.Dialog(this,{ - title: _t("Preferences"), - width: '700px', - buttons: [ - {text: _t("Change password"), click: function(){ self.change_password(); }}, - {text: _t("Cancel"), click: function(){ $(this).dialog('destroy'); }}, - {text: _t("Save"), click: function(){ - var inner_viewmanager = action_manager.inner_viewmanager; - inner_viewmanager.views[inner_viewmanager.active_view].controller.do_save() - .then(function() { - self.dialog.destroy(); - // needs to refresh interface in case language changed - window.location.reload(); - }); - } - } - ] - }).open(); - action_manager.appendTo(this.dialog.$element); - action_manager.renderElement(this.dialog); + } }, on_menu_about: function() { var self = this; @@ -841,45 +908,111 @@ instance.web.UserMenu = instance.web.Widget.extend({ window.location.href, 'debug'); }); instance.web.dialog($help, {autoOpen: true, - modal: true, width: 960, title: _t("About")}); + modal: true, width: 507, height: 290, resizable: false, title: _t("About")}); }); }, }); -instance.web.WebClient = instance.web.Widget.extend({ - init: function(parent) { +instance.web.Client = instance.web.Widget.extend({ + init: function(parent, origin) { + instance.client = instance.webclient = this; + this._super(parent); + this.origin = origin; + }, + start: function() { + var self = this; + return instance.session.session_bind(this.origin).pipe(function() { + var $e = $(QWeb.render(self._template, {})); + self.replaceElement($e); + self.bind_events(); + return self.show_common(); + }); + }, + bind_events: function() { + var self = this; + this.$el.on('mouseenter', '.oe_systray > div:not([data-tipsy=true])', function() { + $(this).attr('data-tipsy', 'true').tipsy().trigger('mouseenter'); + }); + this.$el.on('click', '.oe_dropdown_toggle', function(ev) { + ev.preventDefault(); + var $toggle = $(this); + var $menu = $toggle.siblings('.oe_dropdown_menu'); + $menu = $menu.size() >= 1 ? $menu : $toggle.find('.oe_dropdown_menu'); + var state = $menu.is('.oe_opened'); + setTimeout(function() { + // Do not alter propagation + $toggle.add($menu).toggleClass('oe_opened', !state); + if (!state) { + // Move $menu if outside window's edge + var doc_width = $(document).width(); + var offset = $menu.offset(); + var menu_width = $menu.width(); + var x = doc_width - offset.left - menu_width - 2; + if (x < 0) { + $menu.offset({ left: offset.left + x }).width(menu_width); + } + } + }, 0); + }); + instance.web.bus.on('click', this, function(ev) { + $.fn.tipsy.clear(); + if (!$(ev.target).is('input[type=file]')) { + self.$el.find('.oe_dropdown_menu.oe_opened, .oe_dropdown_toggle.oe_opened').removeClass('oe_opened'); + } + }); + }, + show_common: function() { var self = this; + this.crashmanager = new instance.web.CrashManager(); + instance.session.on_rpc_error.add(this.crashmanager.on_rpc_error); + self.notification = new instance.web.Notification(this); + self.notification.appendTo(self.$el); + self.loading = new instance.web.Loading(self); + self.loading.appendTo(self.$el); + self.action_manager = new instance.web.ActionManager(self); + self.action_manager.appendTo(self.$('.oe_application')); + }, + toggle_bars: function(value) { + this.$('tr:has(td.oe_topbar),.oe_leftbar').toggle(value); + }, + has_uncommitted_changes: function() { + return false; + }, +}); + +instance.web.WebClient = instance.web.Client.extend({ + _template: 'WebClient', + init: function(parent) { this._super(parent); - instance.webclient = this; - this.querystring = '?' + jQuery.param.querystring(); this._current_state = null; }, start: function() { var self = this; - this.$element.addClass("openerp openerp-web-client-container"); - if (jQuery.param !== undefined && jQuery.deparam(jQuery.param.querystring()).kitten !== undefined) { - $("body").addClass("kitten-mode-activated"); - self.$element.delegate('img.oe-record-edit-link-img', 'hover', function(e) { - self.$element.toggleClass('clark-gable'); + return $.when(this._super()).pipe(function() { + self.$el.on('click', '.oe_logo', function() { + self.action_manager.do_action('home'); }); - } - this.session.session_bind().then(function() { - self.destroy_content(); - self.show_common(); + if (jQuery.param !== undefined && jQuery.deparam(jQuery.param.querystring()).kitten !== undefined) { + $("body").addClass("kitten-mode-activated"); + if ($.blockUI) { + $.blockUI.defaults.message = '<img src="http://www.amigrave.com/kitten.gif">'; + } + } if (!self.session.session_is_valid()) { self.show_login(); } else { self.show_application(); } }); - this.$element.on('mouseenter', '.oe_systray > div:not([data-tipsy=true])', function() { - $(this).attr('data-tipsy', 'true').tipsy().trigger('mouseenter'); - }); + }, + set_title: function(title) { + title = _.str.clean(title); + var sep = _.isEmpty(title) ? '' : ' - '; + document.title = title + sep + 'OpenERP'; }, show_common: function() { var self = this; - this.crashmanager = new instance.web.CrashManager(); - instance.connection.on_rpc_error.add(this.crashmanager.on_rpc_error); + this._super(); window.onerror = function (message, file, line) { self.crashmanager.on_traceback({ type: _t("Client Error"), @@ -887,49 +1020,50 @@ instance.web.WebClient = instance.web.Widget.extend({ data: {debug: file + ':' + line} }); }; - self.notification = new instance.web.Notification(this); - self.notification.appendTo(self.$element); - self.loading = new instance.web.Loading(self); - self.loading.appendTo(self.$element); - self.login = new instance.web.Login(self); - self.login.on("login",self,self.show_application); - self.$table = $(QWeb.render("WebClient", {})); - self.action_manager = new instance.web.ActionManager(self); - self.action_manager.appendTo(self.$table.find('.oe_application')); }, show_login: function() { - var self = this; - self.login.appendTo(self.$element); + this.toggle_bars(false); + + var action = { + 'type': 'ir.actions.client', + 'tag': 'login' + }; + var state = $.bbq.getState(true); + if (state.action === "login") { + action.params = state; + } + + this.action_manager.do_action(action); + this.action_manager.inner_widget.on('login_successful', this, function() { + this.do_push_state(state); + this._current_state = null; // ensure the state will be loaded + this.show_application(); // will load the state we just pushed + }); }, show_application: function() { var self = this; - self.$element.append(self.$table); - self.login.$element.hide(); + self.toggle_bars(true); self.menu = new instance.web.Menu(self); - self.menu.replace(this.$element.find('.oe_menu_placeholder')); + self.menu.replace(this.$el.find('.oe_menu_placeholder')); self.menu.on('menu_click', this, this.on_menu_action); self.user_menu = new instance.web.UserMenu(self); - self.user_menu.replace(this.$element.find('.oe_user_menu_placeholder')); + self.user_menu.replace(this.$el.find('.oe_user_menu_placeholder')); self.user_menu.on_menu_logout.add(this.proxy('on_logout')); self.user_menu.on_action.add(this.proxy('on_menu_action')); self.user_menu.do_update(); self.bind_hashchange(); - var version_label = _t("OpenERP - Unsupported/Community Version"); - if (!self.session.openerp_entreprise) { - self.$element.find('.oe_footer_powered').append(_.str.sprintf('<span> - <a href="http://www.openerp.com/support-or-publisher-warranty-contract" target="_blank">%s</a></span>', version_label)); - document.title = version_label; - } + self.set_title(); }, destroy_content: function() { _.each(_.clone(this.getChildren()), function(el) { el.destroy(); }); - this.$element.children().remove(); + this.$el.children().remove(); }, do_reload: function() { var self = this; return this.session.session_reload().pipe(function () { - instance.connection.load_modules(true).pipe( + instance.session.load_modules(true).pipe( self.menu.proxy('do_reload')); }); }, @@ -943,28 +1077,28 @@ instance.web.WebClient = instance.web.Widget.extend({ }, on_logout: function() { var self = this; - this.session.session_logout().then(function () { - $(window).unbind('hashchange', self.on_hashchange); - self.do_push_state({}); - //would be cool to be able to do this, but I think it will make addons do strange things - //this.show_login(); - window.location.reload(); - }); + if (!this.has_uncommitted_changes()) { + this.session.session_logout().then(function () { + $(window).unbind('hashchange', self.on_hashchange); + self.do_push_state({}); + window.location.reload(); + }); + } }, bind_hashchange: function() { var self = this; $(window).bind('hashchange', this.on_hashchange); var state = $.bbq.getState(true); - if (! _.isEmpty(state)) { - $(window).trigger('hashchange'); - } else { + if (_.isEmpty(state) || state.action == "login") { self.menu.has_been_loaded.then(function() { - var first_menu_id = self.menu.$element.find("a:first").data("menu"); + var first_menu_id = self.menu.$el.find("a:first").data("menu"); if(first_menu_id) { self.menu.menu_click(first_menu_id); } }); + } else { + $(window).trigger('hashchange'); } }, on_hashchange: function(event) { @@ -984,12 +1118,24 @@ instance.web.WebClient = instance.web.Widget.extend({ this._current_state = state; }, do_push_state: function(state) { + this.set_title(state.title); + delete state.title; var url = '#' + $.param(state); this._current_state = _.clone(state); $.bbq.pushState(url); }, - on_menu_action: function(action) { - this.action_manager.do_action(action); + on_menu_action: function(options) { + var self = this; + return this.rpc("/web/action/load", { action_id: options.action_id }) + .pipe(function (result) { + var action = result.result; + if (options.needaction) { + action.context.search_default_needaction_pending = true; + } + return $.when(self.action_manager.do_action(action, null, true)).fail(function() { + self.menu.open_menu(options.previous_menu_id); + }); + }); }, do_action: function(action) { var self = this; @@ -1001,38 +1147,55 @@ instance.web.WebClient = instance.web.Widget.extend({ } }, set_content_full_screen: function(fullscreen) { - if (fullscreen) - $(".oe_webclient", this.$element).addClass("oe_content_full_screen"); - else - $(".oe_webclient", this.$element).removeClass("oe_content_full_screen"); - } + if (fullscreen) { + $(".oe_webclient", this.$el).addClass("oe_content_full_screen"); + $("body").css({'overflow-y':'hidden'}); + } else { + $(".oe_webclient", this.$el).removeClass("oe_content_full_screen"); + $("body").css({'overflow-y':'scroll'}); + } + }, + has_uncommitted_changes: function() { + var $e = $.Event('clear_uncommitted_changes'); + instance.web.bus.trigger('clear_uncommitted_changes', $e); + if ($e.isDefaultPrevented()) { + return true; + } else { + return this._super.apply(this, arguments); + } + }, }); -instance.web.EmbeddedClient = instance.web.Widget.extend({ - template: 'EmptyComponent', - init: function(parent, action_id, options) { - this._super(parent); - // TODO take the xmlid of a action instead of its id +instance.web.EmbeddedClient = instance.web.Client.extend({ + _template: 'EmbedClient', + init: function(parent, origin, dbname, login, key, action_id, options) { + this._super(parent, origin); + + this.dbname = dbname; + this.login = login; + this.key = key; this.action_id = action_id; this.options = options || {}; - this.am = new instance.web.ActionManager(this); }, start: function() { var self = this; - this.am.appendTo(this.$element.addClass('openerp')); - return this.rpc("/web/action/load", { action_id: this.action_id }, function(result) { - var action = result.result; - action.flags = _.extend({ - //views_switcher : false, - search_view : false, - action_buttons : false, - sidebar : false - //pager : false - }, self.options, action.flags || {}); + return $.when(this._super()).pipe(function() { + return instance.session.session_authenticate(self.dbname, self.login, self.key, true).pipe(function() { + return self.rpc("/web/action/load", { action_id: self.action_id }, function(result) { + var action = result.result; + action.flags = _.extend({ + //views_switcher : false, + search_view : false, + action_buttons : false, + sidebar : false + //pager : false + }, self.options, action.flags || {}); - self.am.do_action(action); + self.action_manager.do_action(action); + }); + }); }); - } + }, }); instance.web.embed = function (origin, dbname, login, key, action, options) { @@ -1046,13 +1209,8 @@ instance.web.embed = function (origin, dbname, login, key, action, options) { var sc = document.getElementsByTagName('script'); currentScript = sc[sc.length-1]; } - instance.connection.session_bind(origin).then(function () { - instance.connection.session_authenticate(dbname, login, key, true).then(function () { - var client = new instance.web.EmbeddedClient(null, action, options); - client.insertAfter(currentScript); - }); - }); - + var client = new instance.web.EmbeddedClient(null, origin, dbname, login, key, action, options); + client.insertAfter(currentScript); }; }; diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js index 94c748f839c..62ac48f0c61 100644 --- a/addons/web/static/src/js/corelib.js +++ b/addons/web/static/src/js/corelib.js @@ -99,7 +99,6 @@ openerp.web.corelib = function(instance) { for (var name in prop) { // Check if we're overwriting an existing function prototype[name] = typeof prop[name] == "function" && - typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function(name, fn) { return function() { @@ -379,7 +378,8 @@ instance.web.PropertiesMixin = _.extend({}, instance.web.EventDispatcherMixin, { instance.web.EventDispatcherMixin.init.call(this); this.__getterSetterInternalMap = {}; }, - set: function(map) { + set: function(map, options) { + options = options || {}; var self = this; var changed = false; _.each(map, function(val, key) { @@ -388,10 +388,11 @@ instance.web.PropertiesMixin = _.extend({}, instance.web.EventDispatcherMixin, { return; changed = true; self.__getterSetterInternalMap[key] = val; - self.trigger("change:" + key, self, { - oldValue: tmp, - newValue: val - }); + if (! options.silent) + self.trigger("change:" + key, self, { + oldValue: tmp, + newValue: val + }); }); if (changed) self.trigger("change", self); @@ -490,24 +491,20 @@ instance.web.CallbackEnabledMixin = _.extend({}, instance.web.PropertiesMixin, { * * The semantics of this precisely replace closing over the method call. * - * @param {String} method_name name of the method to invoke + * @param {String|Function} method function or name of the method to invoke * @returns {Function} proxied method */ - proxy: function (method_name) { + proxy: function (method) { var self = this; return function () { - return self[method_name].apply(self, arguments); + var fn = (typeof method === 'string') ? self[method] : method; + return fn.apply(self, arguments); } } }); instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { /** - * Tag name when creating a default $element. - * @type string - */ - tagName: 'div', - /** * Constructs the widget and sets its parent if a parent is given. * * @constructs instance.web.Widget @@ -516,14 +513,9 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { * @param {instance.web.Widget} parent Binds the current instance to the given Widget instance. * When that widget is destroyed by calling destroy(), the current instance will be * destroyed too. Can be null. - * @param {String} element_id Deprecated. Sets the element_id. Only useful when you want - * to bind the current Widget to an already existing part of the DOM, which is not compatible - * with the DOM insertion methods provided by the current implementation of Widget. So - * for new components this argument should not be provided any more. */ init: function(parent) { instance.web.CallbackEnabledMixin.init.call(this); - this.$element = $(document.createElement(this.tagName)); this.setParent(parent); }, /** @@ -533,8 +525,8 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { _.each(this.getChildren(), function(el) { el.destroy(); }); - if(this.$element != null) { - this.$element.remove(); + if(this.$el) { + this.$el.remove(); } instance.web.PropertiesMixin.destroy.call(this); }, @@ -546,7 +538,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { appendTo: function(target) { var self = this; return this.__widgetRenderAndInsert(function(t) { - self.$element.appendTo(t); + self.$el.appendTo(t); }, target); }, /** @@ -557,7 +549,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { prependTo: function(target) { var self = this; return this.__widgetRenderAndInsert(function(t) { - self.$element.prependTo(t); + self.$el.prependTo(t); }, target); }, /** @@ -568,7 +560,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { insertAfter: function(target) { var self = this; return this.__widgetRenderAndInsert(function(t) { - self.$element.insertAfter(t); + self.$el.insertAfter(t); }, target); }, /** @@ -579,7 +571,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { insertBefore: function(target) { var self = this; return this.__widgetRenderAndInsert(function(t) { - self.$element.insertBefore(t); + self.$el.insertBefore(t); }, target); }, /** @@ -589,7 +581,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { */ replace: function(target) { return this.__widgetRenderAndInsert(_.bind(function(t) { - this.$element.replaceAll(t); + this.$el.replaceAll(t); }, this), target); }, __widgetRenderAndInsert: function(insertion, target) { @@ -612,6 +604,7 @@ instance.web.WidgetMixin = _.extend({},instance.web.CallbackEnabledMixin, { * @returns {jQuery.Deferred} */ start: function() { + return $.when(); } }); @@ -647,8 +640,8 @@ instance.web.CallbackEnabled = instance.web.Class.extend(instance.web.CallbackEn * // stuff that you want to init before the rendering * }, * start: function() { - * // stuff you want to make after the rendering, `this.$element` holds a correct value - * this.$element.find(".my_button").click(/* an example of event binding * /); + * // stuff you want to make after the rendering, `this.$el` holds a correct value + * this.$el.find(".my_button").click(/* an example of event binding * /); * * // if you have some asynchronous operations, it's a good idea to return * // a promise in start() @@ -672,6 +665,12 @@ instance.web.CallbackEnabled = instance.web.Class.extend(instance.web.CallbackEn * That will kill the widget in a clean way and erase its content from the dom. */ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { + // Backbone-ish API + tagName: 'div', + id: null, + className: null, + attributes: {}, + events: {}, /** * The name of the QWeb template that will be used for rendering. Must be * redefined in subclasses or the default render() method can not be used. @@ -688,14 +687,12 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { * @param {instance.web.Widget} parent Binds the current instance to the given Widget instance. * When that widget is destroyed by calling destroy(), the current instance will be * destroyed too. Can be null. - * @param {String} element_id Deprecated. Sets the element_id. Only useful when you want - * to bind the current Widget to an already existing part of the DOM, which is not compatible - * with the DOM insertion methods provided by the current implementation of Widget. So - * for new components this argument should not be provided any more. */ init: function(parent) { instance.web.WidgetMixin.init.call(this,parent); - this.session = instance.connection; + // FIXME: this should not be + this.setElement(this._make_descriptive()); + this.session = instance.session; }, /** * Renders the element. The default implementation renders the widget using QWeb, @@ -703,20 +700,120 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { * key that references `this`. */ renderElement: function() { - var rendered = null; - if (this.template) - rendered = instance.web.qweb.render(this.template, {widget: this}); - if (_.str.trim(rendered)) { - var elem = $(rendered); - this.$element.replaceWith(elem); - this.$element = elem; + var $el; + if (this.template) { + $el = $(_.str.trim(instance.web.qweb.render( + this.template, {widget: this}))); + } else { + $el = this._make_descriptive(); } + this.replaceElement($el); }, + /** - * Shortcut for $element.find() like backbone + * Re-sets the widget's root element and replaces the old root element + * (if any) by the new one in the DOM. + * + * @param {HTMLElement | jQuery} $el + * @returns {*} this */ - "$": function() { - return this.$element.find.apply(this.$element,arguments); + replaceElement: function ($el) { + var $oldel = this.$el; + this.setElement($el); + if ($oldel && !$oldel.is(this.$el)) { + $oldel.replaceWith(this.$el); + } + return this; + }, + /** + * Re-sets the widget's root element (el/$el/$el). + * + * Includes: + * * re-delegating events + * * re-binding sub-elements + * * if the widget already had a root element, replacing the pre-existing + * element in the DOM + * + * @param {HTMLElement | jQuery} element new root element for the widget + * @return {*} this + */ + setElement: function (element) { + // NB: completely useless, as WidgetMixin#init creates a $el + // always + if (this.$el) { + this.undelegateEvents(); + } + + this.$el = (element instanceof $) ? element : $(element); + this.el = this.$el[0]; + + this.delegateEvents(); + + return this; + }, + /** + * Utility function to build small DOM elements. + * + * @param {String} tagName name of the DOM element to create + * @param {Object} [attributes] map of DOM attributes to set on the element + * @param {String} [content] HTML content to set on the element + * @return {Element} + */ + make: function (tagName, attributes, content) { + var el = document.createElement(tagName); + if (!_.isEmpty(attributes)) { + $(el).attr(attributes); + } + if (content) { + $(el).html(content); + } + return el; + }, + /** + * Makes a potential root element from the declarative builder of the + * widget + * + * @return {jQuery} + * @private + */ + _make_descriptive: function () { + var attrs = _.extend({}, this.attributes || {}); + if (this.id) { attrs.id = this.id; } + if (this.className) { attrs['class'] = this.className; } + return $(this.make(this.tagName, attrs)); + }, + delegateEvents: function () { + var events = this.events; + if (_.isEmpty(events)) { return; } + + for(var key in events) { + if (!events.hasOwnProperty(key)) { continue; } + + var method = this.proxy(events[key]); + + var match = /^(\S+)(\s+(.*))?$/.exec(key); + var event = match[1]; + var selector = match[3]; + + event += '.widget_events'; + if (!selector) { + this.$el.on(event, method); + } else { + this.$el.on(event, selector, method); + } + } + }, + undelegateEvents: function () { + this.$el.off('.widget_events'); + }, + /** + * Shortcut for ``this.$el.find(selector)`` + * + * @param {String} selector CSS selector, rooted in $el + * @returns {jQuery} selector match + */ + $: function(selector) { + return this.$el.find(selector); }, /** * Informs the action manager to do an action. This supposes that @@ -744,7 +841,7 @@ instance.web.Widget = instance.web.Class.extend(instance.web.WidgetMixin, { rpc: function(url, data, success, error) { var def = $.Deferred().then(success, error); var self = this; - instance.connection.rpc(url, data). then(function() { + instance.session.rpc(url, data). then(function() { if (!self.isDestroyed()) def.resolve.apply(def, arguments); }, function() { @@ -766,7 +863,7 @@ instance.web.Registry = instance.web.Class.extend({ * * An object path is simply a dotted name from the instance root to the * object pointed to (e.g. ``"instance.web.Session"`` for an OpenERP - * connection object). + * session object). * * @constructs instance.web.Registry * @param {Object} mapping a mapping of keys to object-paths @@ -875,6 +972,11 @@ instance.web.Registry = instance.web.Class.extend({ }); instance.web.JsonRPC = instance.web.CallbackEnabled.extend({ + triggers: { + 'request': 'Request sent', + 'response': 'Response received', + 'error': 'HTTP Error response or timeout received', + }, /** * @constructs instance.web.JsonRPC * @extends instance.web.CallbackEnabled @@ -1038,7 +1140,8 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({ uid: new py.float(this.uid), datetime: datetime, time: time, - relativedelta: relativedelta + relativedelta: relativedelta, + current_date: date.today.__call__().strftime(['%Y-%m-%d']) }; }, /** @@ -1212,12 +1315,12 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({ id: _.uniqueId('r') }; var deferred = $.Deferred(); - this.on_rpc_request(); + this.trigger('request', url, payload); var aborter = params.aborter; delete params.aborter; var request = this.rpc_function(url, payload).then( function (response, textStatus, jqXHR) { - self.on_rpc_response(); + self.trigger('response', response); if (!response.error) { if (url.url === '/web/session/eval_domain_and_context') { self.test_eval(params, response.result); @@ -1225,18 +1328,12 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({ deferred.resolve(response["result"], textStatus, jqXHR); } else if (response.error.data.type === "session_invalid") { self.uid = false; - // TODO deprecate or use a deferred on login.do_ask_login() - self.on_session_invalid(function() { - self.rpc(url, payload.params, - function() { deferred.resolve.apply(deferred, arguments); }, - function() { deferred.reject.apply(deferred, arguments); }); - }); } else { deferred.reject(response.error, $.Event()); } }, function(jqXHR, textStatus, errorThrown) { - self.on_rpc_response(); + self.trigger('error'); var error = { code: -32098, message: "XmlHttpRequestError " + errorThrown, @@ -1279,7 +1376,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({ processData: false }, url); if (this.synch) - ajax.async = false; + ajax.async = false; return $.ajax(ajax); }, rpc_jsonp: function(url, payload) { @@ -1298,7 +1395,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({ data: data }, url); if (this.synch) - ajax.async = false; + ajax.async = false; var payload_str = JSON.stringify(payload); var payload_url = $.param({r:payload_str}); if(payload_url.length < 2000) { @@ -1308,19 +1405,19 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({ } else { // Indirect jsonp request var ifid = _.uniqueId('oe_rpc_iframe'); - var display = options.openerp.debug ? 'block' : 'none'; + var display = self.debug ? 'block' : 'none'; var $iframe = $(_.str.sprintf("<iframe src='javascript:false;' name='%s' id='%s' style='display:%s'></iframe>", ifid, ifid, display)); var $form = $('<form>') .attr('method', 'POST') .attr('target', ifid) .attr('enctype', "multipart/form-data") - .attr('action', ajax.url + '?' + $.param(data)) + .attr('action', ajax.url + '?jsonp=1&' + $.param(data)) .append($('<input type="hidden" name="r" />').attr('value', payload_str)) .hide() .appendTo($('body')); var cleanUp = function() { if ($iframe) { - $iframe.unbind("load").attr("src", "javascript:false;").remove(); + $iframe.unbind("load").remove(); } $form.remove(); }; @@ -1344,10 +1441,6 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({ return deferred; } }, - on_rpc_request: function() { - }, - on_rpc_response: function() { - }, on_rpc_error: function(error) { }, get_url: function (file) { diff --git a/addons/web/static/src/js/coresetup.js b/addons/web/static/src/js/coresetup.js index 4defde54a3c..ea160d6faee 100644 --- a/addons/web/static/src/js/coresetup.js +++ b/addons/web/static/src/js/coresetup.js @@ -11,33 +11,6 @@ if (!console.debug) { openerp.web.coresetup = function(instance) { -/** - * @deprecated use :class:`instance.web.Widget` - */ -instance.web.OldWidget = instance.web.Widget.extend({ - init: function(parent, element_id) { - this._super(parent); - this.element_id = element_id; - this.element_id = this.element_id || _.uniqueId('widget-'); - var tmp = document.getElementById(this.element_id); - this.$element = tmp ? $(tmp) : $(document.createElement(this.tagName)); - }, - renderElement: function() { - var rendered = this.render(); - if (rendered) { - var elem = $(rendered); - this.$element.replaceWith(elem); - this.$element = elem; - } - return this; - }, - render: function (additional) { - if (this.template) - return instance.web.qweb.render(this.template, _.extend({widget: this}, additional || {})); - return null; - } -}); - /** Session openerp specific RPC class */ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Session# */{ init: function() { @@ -62,7 +35,6 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess this.username = false; this.user_context= {}; this.db = false; - this.openerp_entreprise = false; this.module_list = instance._modules.slice(); this.module_loaded = {}; _(this.module_list).each(function (mod) { @@ -105,8 +77,7 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess db: result.db, username: result.login, uid: result.uid, - user_context: result.context, - openerp_entreprise: result.openerp_entreprise + user_context: result.context }); }); }, @@ -130,8 +101,7 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess db: result.db, username: result.login, uid: result.uid, - user_context: result.context, - openerp_entreprise: result.openerp_entreprise + user_context: result.context }); if (!_volatile) { self.set_cookie('session_id', self.session_id); @@ -143,20 +113,6 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess this.set_cookie('session_id', ''); return this.rpc("/web/session/destroy", {}); }, - on_session_valid: function() { - }, - /** - * Called when a rpc call fail due to an invalid session. - * By default, it's a noop - */ - on_session_invalid: function(retry_callback) { - }, - /** - * Fetches a cookie stored by an openerp session - * - * @private - * @param name the cookie's name - */ get_cookie: function (name) { if (!this.name) { return null; } var nameEQ = this.name + '|' + name + '='; @@ -190,9 +146,8 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess /** * Load additional web addons of that instance and init them * - * @param {Boolean} [no_session_valid_signal=false] prevents load_module from triggering ``on_session_valid``. */ - load_modules: function(no_session_valid_signal) { + load_modules: function() { var self = this; return this.rpc('/web/session/modules', {}).pipe(function(result) { var lang = self.user_context.lang, @@ -201,31 +156,31 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess var to_load = _.difference(result, self.module_list).join(','); self.module_list = all_modules; - var loaded = $.Deferred().resolve().promise(); - if (to_load.length) { + var loaded = self.rpc('/web/webclient/translations', params).then(function(trans) { + instance.web._t.database.set_bundle(trans); + }); + var file_list = ["/web/static/lib/datejs/globalization/" + lang.replace("_", "-") + ".js"]; + if(to_load.length) { loaded = $.when( - self.rpc('/web/webclient/csslist', {mods: to_load}, self.do_load_css), + loaded, + self.rpc('/web/webclient/csslist', {mods: to_load}).then(self.do_load_css), self.rpc('/web/webclient/qweblist', {mods: to_load}).pipe(self.do_load_qweb), - self.rpc('/web/webclient/translations', params).pipe(function(trans) { - instance.web._t.database.set_bundle(trans); - var file_list = ["/web/static/lib/datejs/globalization/" + lang.replace("_", "-") + ".js"]; - return self.rpc('/web/webclient/jslist', {mods: to_load}).pipe(function(files) { - return self.do_load_js(file_list.concat(files)); - }).then(function () { - if (!Date.CultureInfo.pmDesignator) { - // If no am/pm designator is specified but the openerp - // datetime format uses %i, date.js won't be able to - // correctly format a date. See bug#938497. - Date.CultureInfo.amDesignator = 'AM'; - Date.CultureInfo.pmDesignator = 'PM'; - } - }); - })) + self.rpc('/web/webclient/jslist', {mods: to_load}).then(function(files) { + file_list = file_list.concat(files); + }) + ); } - return loaded.then(function() { + return loaded.pipe(function () { + return self.do_load_js(file_list); + }).then(function() { self.on_modules_loaded(); - if (!no_session_valid_signal) { - self.on_session_valid(); + self.trigger('module_loaded'); + if (!Date.CultureInfo.pmDesignator) { + // If no am/pm designator is specified but the openerp + // datetime format uses %i, date.js won't be able to + // correctly format a date. See bug#938497. + Date.CultureInfo.amDesignator = 'AM'; + Date.CultureInfo.pmDesignator = 'PM'; } }); }); @@ -333,11 +288,14 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess .appendTo(document.body) .load(function () { try { - if (options.error) { - options.error(JSON.parse( - this.contentDocument.body.childNodes[1].textContent - )); - } + if (options.error) { + if (!this.contentDocument.body.childNodes[1]) { + options.error(this.contentDocument.body.childNodes); + } + else { + options.error(JSON.parse(this.contentDocument.body.childNodes[1].textContent)); + } + } } finally { complete(); } @@ -390,16 +348,41 @@ instance.web.Session = instance.web.JsonRPC.extend( /** @lends instance.web.Sess timer = setTimeout(waitLoop, CHECK_INTERVAL); }, synchronized_mode: function(to_execute) { - var synch = this.synch; - this.synch = true; - try { - return to_execute(); - } finally { - this.synch = synch; - } + var synch = this.synch; + this.synch = true; + try { + return to_execute(); + } finally { + this.synch = synch; + } } }); + +/** + * Event Bus used to bind events scoped in the current instance + */ +instance.web.Bus = instance.web.Class.extend(instance.web.EventDispatcherMixin, { + init: function() { + instance.web.EventDispatcherMixin.init.call(this, parent); + var self = this; + // TODO fme: allow user to bind keys for some global actions. + // check gtk bindings + // http://unixpapa.com/js/key.html + _.each('click,dblclick,keydown,keypress,keyup'.split(','), function(evtype) { + $('html').on(evtype, function(ev) { + self.trigger(evtype, ev); + }); + }); + _.each('resize,scroll'.split(','), function(evtype) { + $(window).on(evtype, function(ev) { + self.trigger(evtype, ev); + }); + }); + } +}) +instance.web.bus = new instance.web.Bus(); + /** OpenERP Translations */ instance.web.TranslationDataBase = instance.web.Class.extend(/** @lends instance.web.TranslationDataBase# */{ /** @@ -454,64 +437,6 @@ instance.web.TranslationDataBase = instance.web.Class.extend(/** @lends instance } }); -/** Configure default qweb */ -instance.web._t = new instance.web.TranslationDataBase().build_translation_function(); -/** - * Lazy translation function, only performs the translation when actually - * printed (e.g. inserted into a template) - * - * Useful when defining translatable strings in code evaluated before the - * translation database is loaded, as class attributes or at the top-level of - * an OpenERP Web module - * - * @param {String} s string to translate - * @returns {Object} lazy translation object - */ -instance.web._lt = function (s) { - return {toString: function () { return instance.web._t(s); }} -}; -instance.web.qweb = new QWeb2.Engine(); -instance.web.qweb.debug = ($.deparam($.param.querystring()).debug != undefined); -instance.web.qweb.default_dict = { - '_' : _, - '_t' : instance.web._t -}; -instance.web.qweb.preprocess_node = function() { - // Note that 'this' is the Qweb Node - switch (this.node.nodeType) { - case 3: - case 4: - // Text and CDATAs - var translation = this.node.parentNode.attributes['t-translation']; - if (translation && translation.value === 'off') { - return; - } - var ts = _.str.trim(this.node.data); - if (ts.length === 0) { - return; - } - var tr = instance.web._t(ts); - if (tr !== ts) { - this.node.data = tr; - } - break; - case 1: - // Element - var attr, attrs = ['label', 'title', 'alt']; - while (attr = attrs.pop()) { - if (this.attributes[attr]) { - this.attributes[attr] = instance.web._t(this.attributes[attr]); - } - } - } -}; - -/** Configure blockui */ -if ($.blockUI) { - $.blockUI.defaults.baseZ = 1100; - $.blockUI.defaults.message = '<img src="/web/static/src/img/throbber2.gif">'; -} - /** Custom jQuery plugins */ $.fn.getAttributes = function() { var o = {}; @@ -541,10 +466,6 @@ $.Mutex = (function() { return Mutex; })(); -/** Setup default session */ -instance.connection = new instance.web.Session(); -instance.web.qweb.default_dict['__debug__'] = instance.connection.debug; - $.async_when = function() { var async = false; var def = $.Deferred(); @@ -574,38 +495,180 @@ $.async_when = function() { // special tweak for the web client var old_async_when = $.async_when; $.async_when = function() { - if (instance.connection.synch) - return $.when.apply(this, arguments); - else - return old_async_when.apply(this, arguments); + if (instance.session.synch) + return $.when.apply(this, arguments); + else + return old_async_when.apply(this, arguments); }; +/** Setup default session */ +instance.session = new instance.web.Session(); + +/** Configure default qweb */ +instance.web._t = new instance.web.TranslationDataBase().build_translation_function(); /** - * Registry for all the client actions key: tag value: widget + * Lazy translation function, only performs the translation when actually + * printed (e.g. inserted into a template) + * + * Useful when defining translatable strings in code evaluated before the + * translation database is loaded, as class attributes or at the top-level of + * an OpenERP Web module + * + * @param {String} s string to translate + * @returns {Object} lazy translation object */ -instance.web.client_actions = new instance.web.Registry(); +instance.web._lt = function (s) { + return {toString: function () { return instance.web._t(s); }} +}; +instance.web.qweb = new QWeb2.Engine(); +instance.web.qweb.default_dict['__debug__'] = instance.session.debug; // Which one ? +instance.web.qweb.debug = instance.session.debug; +instance.web.qweb.default_dict = { + '_' : _, + '_t' : instance.web._t +}; +instance.web.qweb.preprocess_node = function() { + // Note that 'this' is the Qweb Node + switch (this.node.nodeType) { + case 3: + case 4: + // Text and CDATAs + var translation = this.node.parentNode.attributes['t-translation']; + if (translation && translation.value === 'off') { + return; + } + var ts = _.str.trim(this.node.data); + if (ts.length === 0) { + return; + } + var tr = instance.web._t(ts); + if (tr !== ts) { + this.node.data = tr; + } + break; + case 1: + // Element + var attr, attrs = ['label', 'title', 'alt', 'placeholder']; + while (attr = attrs.pop()) { + if (this.attributes[attr]) { + this.attributes[attr] = instance.web._t(this.attributes[attr]); + } + } + } +}; -/** - * Client action to reload the whole interface. - * If params has an entry 'menu_id', it opens the given menu entry. +/** Setup jQuery timeago */ +var _t = instance.web._t; +/* + * Strings in timeago are "composed" with prefixes, words and suffixes. This + * makes their detection by our translating system impossible. Use all literal + * strings we're using with a translation mark here so the extractor can do its + * job. */ -instance.web.client_actions.add("reload", "instance.web.Reload"); +{ + _t('less than a minute ago'); + _t('about a minute ago'); + _t('%d minutes ago'); + _t('about an hour ago'); + _t('%d hours ago'); + _t('a day ago'); + _t('%d days ago'); + _t('about a month ago'); + _t('%d months ago'); + _t('about a year ago'); + _t('%d years ago'); +} -instance.web.Reload = instance.web.Widget.extend({ - init: function(parent, params) { - this._super(parent); - this.menu_id = (params && params.menu_id) || false; - }, +instance.session.on('module_loaded', this, function () { + // provide timeago.js with our own translator method + $.timeago.settings.translator = instance.web._t; +}); + +/** Setup blockui */ +if ($.blockUI) { + $.blockUI.defaults.baseZ = 1100; + $.blockUI.defaults.message = '<div class="openerp oe_blockui_spin_container" style="background-color: transparent;">'; + $.blockUI.defaults.css.border = '0'; + $.blockUI.defaults.css["background-color"] = ''; +} + +var messages_by_seconds = function() { + return [ + [0, _t("Loading...")], + [20, _t("Still loading...")], + [60, _t("Still loading...<br />Please be patient.")], + [120, _t("Don't leave yet,<br />it's still loading...")], + [300, _t("You may not believe it,<br />but the application is actually loading...")], + [420, _t("Take a minute to get a coffee,<br />because it's loading...")], + [600, _t("It's loading...<br />By the way, did you tried the kitten mode?")], + [3600, _t("Maybe you should consider pressing F5...")], + ]; +}; + +instance.web.Throbber = instance.web.Widget.extend({ + template: "Throbber", start: function() { - if (this.menu_id) { - // open the given menu id - var url_without_fragment = window.location.toString().split("#", 1)[0]; - window.location = url_without_fragment + "#menu_id=" + this.menu_id; - } else { - window.location.reload(); - } - } + var opts = { + lines: 13, // The number of lines to draw + length: 7, // The length of each line + width: 4, // The line thickness + radius: 10, // The radius of the inner circle + rotate: 0, // The rotation offset + color: '#FFF', // #rgb or #rrggbb + speed: 1, // Rounds per second + trail: 60, // Afterglow percentage + shadow: false, // Whether to render a shadow + hwaccel: false, // Whether to use hardware acceleration + className: 'spinner', // The CSS class to assign to the spinner + zIndex: 2e9, // The z-index (defaults to 2000000000) + top: 'auto', // Top position relative to parent in px + left: 'auto' // Left position relative to parent in px + }; + this.spin = new Spinner(opts).spin(this.$el[0]); + this.start_time = new Date().getTime(); + this.act_message(); + }, + act_message: function() { + var self = this; + setTimeout(function() { + if (self.isDestroyed()) + return; + var seconds = (new Date().getTime() - self.start_time) / 1000; + var mes; + _.each(messages_by_seconds(), function(el) { + if (seconds >= el[0]) + mes = el[1]; + }); + self.$(".oe_throbber_message").html(mes); + self.act_message(); + }, 1000); + }, + destroy: function() { + if (this.spin) + this.spin.stop(); + this._super(); + }, }); +instance.web.Throbber.throbbers = []; + +instance.web.blockUI = function() { + var tmp = $.blockUI.apply($, arguments); + var throbber = new instance.web.Throbber(); + instance.web.Throbber.throbbers.push(throbber); + throbber.appendTo($(".oe_blockui_spin_container")); + return tmp; +} +instance.web.unblockUI = function() { + _.each(instance.web.Throbber.throbbers, function(el) { + el.destroy(); + }); + return $.unblockUI.apply($, arguments); +} + +/** + * Registry for all the client actions key: tag value: widget + */ +instance.web.client_actions = new instance.web.Registry(); }; diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js index fdb15d598af..f1f8532821f 100644 --- a/addons/web/static/src/js/data.js +++ b/addons/web/static/src/js/data.js @@ -58,7 +58,7 @@ instance.web.Query = instance.web.Class.extend({ }, _execute: function () { var self = this; - return instance.connection.rpc('/web/dataset/search_read', { + return instance.session.rpc('/web/dataset/search_read', { model: this._model.name, fields: this._fields || false, domain: this._model.domain(this._filter), @@ -107,7 +107,7 @@ instance.web.Query = instance.web.Class.extend({ * Performs a groups read according to the provided grouping criterion * * @param {String|Array<String>} grouping - * @returns {jQuery.Deferred<Array<openerp.web.data.Group>> | null} + * @returns {jQuery.Deferred<Array<openerp.web.QueryGroup>> | null} */ group_by: function (grouping) { if (grouping === undefined) { @@ -130,7 +130,7 @@ instance.web.Query = instance.web.Class.extend({ orderby: instance.web.serialize_sort(this._order_by) || false }).pipe(function (results) { return _(results).map(function (result) { - return new instance.web.data.Group( + return new instance.web.QueryGroup( self._model.name, grouping[0], result); }); }); @@ -194,7 +194,54 @@ instance.web.Query = instance.web.Class.extend({ } }); -instance.web.Model = instance.web.Class.extend(/** @lends openerp.web.Model# */{ +instance.web.QueryGroup = instance.web.Class.extend({ + init: function (model, grouping_field, read_group_group) { + // In cases where group_by_no_leaf and no group_by, the result of + // read_group has aggregate fields but no __context or __domain. + // Create default (empty) values for those so that things don't break + var fixed_group = _.extend( + {__context: {group_by: []}, __domain: []}, + read_group_group); + + var aggregates = {}; + _(fixed_group).each(function (value, key) { + if (key.indexOf('__') === 0 + || key === grouping_field + || key === grouping_field + '_count') { + return; + } + aggregates[key] = value || 0; + }); + + this.model = new instance.web.Model( + model, fixed_group.__context, fixed_group.__domain); + + var group_size = fixed_group[grouping_field + '_count'] || fixed_group.__count || 0; + var leaf_group = fixed_group.__context.group_by.length === 0; + this.attributes = { + folded: !!(fixed_group.__fold), + grouped_on: grouping_field, + // if terminal group (or no group) and group_by_no_leaf => use group.__count + length: group_size, + value: fixed_group[grouping_field], + // A group is open-able if it's not a leaf in group_by_no_leaf mode + has_children: !(leaf_group && fixed_group.__context['group_by_no_leaf']), + + aggregates: aggregates + }; + }, + get: function (key) { + return this.attributes[key]; + }, + subgroups: function () { + return this.model.query().group_by(this.model.context().group_by); + }, + query: function () { + return this.model.query.apply(this.model, arguments); + } +}); + +instance.web.Model = instance.web.Class.extend({ /** * @constructs instance.web.Model * @extends instance.web.Class @@ -233,7 +280,7 @@ instance.web.Model = instance.web.Class.extend(/** @lends openerp.web.Model# */{ kwargs = args; args = []; } - return instance.connection.rpc('/web/dataset/call_kw', { + return instance.session.rpc('/web/dataset/call_kw', { model: this.name, method: method, args: args, @@ -256,7 +303,7 @@ instance.web.Model = instance.web.Class.extend(/** @lends openerp.web.Model# */{ * @param {String} signal signal to trigger on the workflow */ exec_workflow: function (id, signal) { - return instance.connection.rpc('/web/dataset/exec_workflow', { + return instance.session.rpc('/web/dataset/exec_workflow', { model: this.name, id: id, signal: signal @@ -282,7 +329,7 @@ instance.web.Model = instance.web.Class.extend(/** @lends openerp.web.Model# */{ */ context: function (context) { return new instance.web.CompoundContext( - instance.connection.user_context, this._context, context || {}); + instance.session.user_context, this._context, context || {}); }, /** * Button action caller, needs to perform cleanup if an action is returned @@ -292,7 +339,7 @@ instance.web.Model = instance.web.Class.extend(/** @lends openerp.web.Model# */{ * FIXME: remove when evaluator integrated */ call_button: function (method, args) { - return instance.connection.rpc('/web/dataset/call_button', { + return instance.session.rpc('/web/dataset/call_button', { model: this.name, method: method, domain_id: null, @@ -302,7 +349,7 @@ instance.web.Model = instance.web.Class.extend(/** @lends openerp.web.Model# */{ }, }); -instance.web.Traverser = instance.web.Class.extend(/** @lends openerp.web.Traverser# */{ +instance.web.Traverser = instance.web.Class.extend({ /** * @constructs instance.web.Traverser * @extends instance.web.Class @@ -381,65 +428,7 @@ instance.web.Traverser = instance.web.Class.extend(/** @lends openerp.web.Traver }); -/** - * Utility objects, should never need to be instantiated from outside of this - * module - * - * @namespace - */ -instance.web.data = { - Group: instance.web.Class.extend(/** @lends openerp.web.data.Group# */{ - /** - * @constructs instance.web.data.Group - * @extends instance.web.Class - */ - init: function (model, grouping_field, read_group_group) { - // In cases where group_by_no_leaf and no group_by, the result of - // read_group has aggregate fields but no __context or __domain. - // Create default (empty) values for those so that things don't break - var fixed_group = _.extend( - {__context: {group_by: []}, __domain: []}, - read_group_group); - - var aggregates = {}; - _(fixed_group).each(function (value, key) { - if (key.indexOf('__') === 0 - || key === grouping_field - || key === grouping_field + '_count') { - return; - } - aggregates[key] = value || 0; - }); - - this.model = new instance.web.Model( - model, fixed_group.__context, fixed_group.__domain); - - var group_size = fixed_group[grouping_field + '_count'] || fixed_group.__count || 0; - var leaf_group = fixed_group.__context.group_by.length === 0; - this.attributes = { - grouped_on: grouping_field, - // if terminal group (or no group) and group_by_no_leaf => use group.__count - length: group_size, - value: fixed_group[grouping_field], - // A group is open-able if it's not a leaf in group_by_no_leaf mode - has_children: !(leaf_group && fixed_group.__context['group_by_no_leaf']), - - aggregates: aggregates - }; - }, - get: function (key) { - return this.attributes[key]; - }, - subgroups: function () { - return this.model.query().group_by(this.model.context().group_by); - }, - query: function () { - return this.model.query.apply(this.model, arguments); - } - }) -}; - -instance.web.DataGroup = instance.web.OldWidget.extend( /** @lends openerp.web.DataGroup# */{ +instance.web.DataGroup = instance.web.CallbackEnabled.extend({ /** * Management interface between views and grouped collections of OpenERP * records. @@ -451,9 +440,9 @@ instance.web.DataGroup = instance.web.OldWidget.extend( /** @lends openerp.web. * content of the current grouping level. * * @constructs instance.web.DataGroup - * @extends instance.web.OldWidget + * @extends instance.web.CallbackEnabled * - * @param {instance.web.OldWidget} parent widget + * @param {instance.web.CallbackEnabled} parent widget * @param {String} model name of the model managed by this DataGroup * @param {Array} domain search domain for this DataGroup * @param {Object} context context of the DataGroup's searches @@ -471,49 +460,47 @@ instance.web.DataGroup = instance.web.OldWidget.extend( /** @lends openerp.web. }, list: function (fields, ifGroups, ifRecords) { var self = this; - $.when(this.model.query(fields) - .order_by(this.sort) - .group_by(this.group_by)).then(function (groups) { - if (!groups) { - ifRecords(_.extend( - new instance.web.DataSetSearch( - self, self.model.name, - self.model.context(), - self.model.domain()), - {_sort: self.sort})); + var query = this.model.query(fields).order_by(this.sort).group_by(this.group_by); + $.when(query).then(function (querygroups) { + // leaf node + if (!querygroups) { + var ds = new instance.web.DataSetSearch(self, self.model.name, self.model.context(), self.model.domain()); + ds._sort = self.sort; + ifRecords(ds); return; } - ifGroups(_(groups).map(function (group) { + // internal node + var child_datagroups = _(querygroups).map(function (group) { var child_context = _.extend( {}, self.model.context(), group.model.context()); - return _.extend( - new instance.web.DataGroup( - self, self.model.name, group.model.domain(), - child_context, group.model._context.group_by, - self.level + 1), - { - __context: child_context, - __domain: group.model.domain(), - grouped_on: group.get('grouped_on'), - length: group.get('length'), - value: group.get('value'), - openable: group.get('has_children'), - aggregates: group.get('aggregates') - }, {sort: self.sort}); - })); + var child_dg = new instance.web.DataGroup( + self, self.model.name, group.model.domain(), + child_context, group.model._context.group_by, + self.level + 1); + child_dg.sort = self.sort; + // copy querygroup properties + child_dg.__context = child_context; + child_dg.__domain = group.model.domain(); + child_dg.folded = group.get('folded'); + child_dg.grouped_on = group.get('grouped_on'); + child_dg.length = group.get('length'); + child_dg.value = group.get('value'); + child_dg.openable = group.get('has_children'); + child_dg.aggregates = group.get('aggregates'); + return child_dg; + }); + ifGroups(child_datagroups); }); } }); -instance.web.ContainerDataGroup = instance.web.DataGroup.extend({ }); -instance.web.GrouplessDataGroup = instance.web.DataGroup.extend({ }); -instance.web.StaticDataGroup = instance.web.GrouplessDataGroup.extend( /** @lends openerp.web.StaticDataGroup# */ { +instance.web.StaticDataGroup = instance.web.DataGroup.extend({ /** - * A specialization of groupless data groups, relying on a single static + * A specialization of data groups, relying on a single static * dataset as its records provider. * * @constructs instance.web.StaticDataGroup - * @extends instance.web.GrouplessDataGroup + * @extends instance.web.DataGroup * @param {openep.web.DataSetStatic} dataset a static dataset backing the groups */ init: function (dataset) { @@ -524,13 +511,13 @@ instance.web.StaticDataGroup = instance.web.GrouplessDataGroup.extend( /** @lend } }); -instance.web.DataSet = instance.web.OldWidget.extend( /** @lends openerp.web.DataSet# */{ +instance.web.DataSet = instance.web.CallbackEnabled.extend({ /** * DateaManagement interface between views and the collection of selected * OpenERP records (represents the view's state?) * * @constructs instance.web.DataSet - * @extends instance.web.OldWidget + * @extends instance.web.CallbackEnabled * * @param {String} model the OpenERP model this dataset will manage */ @@ -648,11 +635,9 @@ instance.web.DataSet = instance.web.OldWidget.extend( /** @lends openerp.web.Da * @param {Function} error_callback function called in case of creation error * @returns {$.Deferred} */ - create: function(data, callback, error_callback) { - return this._model.call('create', - [data], {context: this._model.context()}) - .pipe(function (r) { return {result: r}; }) - .then(callback, error_callback); + create: function(data) { + return this._model.call('create', [data], {context: this._model.context()}) + .pipe(function (r) { return {result: r}; }); }, /** * Saves the provided data in an existing db record @@ -677,10 +662,8 @@ instance.web.DataSet = instance.web.OldWidget.extend( /** @lends openerp.web.Da * @param {Function} callback function called with operation result * @param {Function} error_callback function called in case of deletion error */ - unlink: function(ids, callback, error_callback) { - return this._model.call('unlink', - [ids], {context: this._model.context()}) - .then(callback, error_callback); + unlink: function(ids) { + return this._model.call('unlink', [ids], {context: this._model.context()}); }, /** * Calls an arbitrary RPC method @@ -705,14 +688,14 @@ instance.web.DataSet = instance.web.OldWidget.extend( /** @lends openerp.web.Da * @param {Function} error_callback * @returns {$.Deferred} */ - call_and_eval: function (method, args, domain_index, context_index, callback, error_callback) { - return this.rpc('/web/dataset/call', { + call_and_eval: function (method, args, domain_index, context_index) { + return instance.session.rpc('/web/dataset/call', { model: this.model, method: method, domain_id: domain_index == undefined ? null : domain_index, context_id: context_index == undefined ? null : context_index, args: args || [] - }, callback, error_callback); + }); }, /** * Calls a button method, usually returning some sort of action @@ -723,9 +706,8 @@ instance.web.DataSet = instance.web.OldWidget.extend( /** @lends openerp.web.Da * @param {Function} error_callback * @returns {$.Deferred} */ - call_button: function (method, args, callback, error_callback) { - return this._model.call_button(method, args) - .then(callback, error_callback); + call_button: function (method, args) { + return this._model.call_button(method, args); }, /** * Fetches the "readable name" for records, based on intrinsic rules @@ -734,10 +716,8 @@ instance.web.DataSet = instance.web.OldWidget.extend( /** @lends openerp.web.Da * @param {Function} callback * @returns {$.Deferred} */ - name_get: function(ids, callback) { - return this._model.call('name_get', - [ids], {context: this._model.context()}) - .then(callback); + name_get: function(ids) { + return this._model.call('name_get', [ids], {context: this._model.context()}); }, /** * @@ -748,28 +728,25 @@ instance.web.DataSet = instance.web.OldWidget.extend( /** @lends openerp.web.Da * @param {Function} callback function to call with name_search result * @returns {$.Deferred} */ - name_search: function (name, domain, operator, limit, callback) { + name_search: function (name, domain, operator, limit) { return this._model.call('name_search', { name: name || '', args: domain || false, operator: operator || 'ilike', context: this._model.context(), limit: limit || 0 - }).then(callback); + }); }, /** * @param name * @param callback */ - name_create: function(name, callback) { - return this._model.call('name_create', - [name], {context: this._model.context()}) - .then(callback); + name_create: function(name) { + return this._model.call('name_create', [name], {context: this._model.context()}); }, - exec_workflow: function (id, signal, callback) { + exec_workflow: function (id, signal) { return this._model.exec_workflow(id, signal) - .pipe(function (result) { return { result: result }; }) - .then(callback); + .pipe(function (result) { return { result: result }; }); }, get_context: function(request_context) { return this._model.context(request_context); @@ -803,7 +780,23 @@ instance.web.DataSet = instance.web.OldWidget.extend( /** @lends openerp.web.Da return this.ids.length; }, alter_ids: function(n_ids) { - this.ids = n_ids; + this.ids = n_ids; + }, + /** + * Resequence records. + * + * @param {Array} ids identifiers of the records to resequence + * @returns {$.Deferred} + */ + resequence: function (ids, options) { + options = options || {}; + return instance.session.rpc('/web/dataset/resequence', { + model: this.model, + ids: ids, + context: this._model.context(options.context), + }).pipe(function (results) { + return results; + }); }, }); instance.web.DataSetStatic = instance.web.DataSet.extend({ @@ -836,7 +829,7 @@ instance.web.DataSetStatic = instance.web.DataSet.extend({ this.set_ids(_.without.apply(null, [this.ids].concat(ids))); } }); -instance.web.DataSetSearch = instance.web.DataSet.extend(/** @lends openerp.web.DataSetSearch */{ +instance.web.DataSetSearch = instance.web.DataSet.extend({ /** * @constructs instance.web.DataSetSearch * @extends instance.web.DataSet @@ -886,7 +879,7 @@ instance.web.DataSetSearch = instance.web.DataSet.extend(/** @lends openerp.web }, unlink: function(ids, callback, error_callback) { var self = this; - return this._super(ids, function(result) { + return this._super(ids).then(function(result) { self.ids = _(self.ids).difference(ids); if (self._length) { self._length -= 1; @@ -895,9 +888,7 @@ instance.web.DataSetSearch = instance.web.DataSet.extend(/** @lends openerp.web self.index = self.index <= self.ids.length - 1 ? self.index : (self.ids.length > 0 ? self.ids.length -1 : 0); } - if (callback) - callback(result); - }, error_callback); + }); }, size: function () { if (this._length !== undefined) { @@ -920,14 +911,12 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ on_default_get: function(res) { this.last_default_get = res; }, - create: function(data, callback, error_callback) { + create: function(data) { var cached = {id:_.uniqueId(this.virtual_id_prefix), values: data, defaults: this.last_default_get}; this.to_create.push(_.extend(_.clone(cached), {values: _.clone(cached.values)})); this.cache.push(cached); - var prom = $.Deferred().then(callback); - prom.resolve({result: cached.id}); - return prom.promise(); + return $.Deferred().resolve({result: cached.id}).promise(); }, write: function (id, data, options, callback) { var self = this; @@ -1042,7 +1031,7 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ self.cache.push({id: id, values: record}); } else { // I assume cache value is prioritary - cached.values = _.defaults(_.clone(cached.values), record); + cached.values = _.defaults(_.clone(cached.values), record); } }); return_records(); @@ -1053,7 +1042,7 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ } return completion.promise(); }, - call_button: function (method, args, callback, error_callback) { + call_button: function (method, args) { var id = args[0][0], index; for(var i=0, len=this.cache.length; i<len; ++i) { var record = this.cache[i]; @@ -1064,10 +1053,10 @@ instance.web.BufferedDataSet = instance.web.DataSetStatic.extend({ break; } } - return this._super(method, args, callback, error_callback); + return this._super(method, args); }, alter_ids: function(n_ids) { - this._super(n_ids); + this._super(n_ids); this.on_change(); }, }); @@ -1096,9 +1085,9 @@ instance.web.ProxyDataSet = instance.web.DataSetSearch.extend({ return this._super.apply(this, arguments); } }, - create: function(data, callback, error_callback) { + create: function(data) { if (this.create_function) { - return this.create_function(data, this._super).then(callback, error_callback); + return this.create_function(data, this._super); } else { return this._super.apply(this, arguments); } @@ -1110,9 +1099,9 @@ instance.web.ProxyDataSet = instance.web.DataSetSearch.extend({ return this._super.apply(this, arguments); } }, - unlink: function(ids, callback, error_callback) { + unlink: function(ids) { if (this.unlink_function) { - return this.unlink_function(ids, this._super).then(callback, error_callback); + return this.unlink_function(ids, this._super); } else { return this._super.apply(this, arguments); } @@ -1165,7 +1154,7 @@ instance.web.CompoundDomain = instance.web.Class.extend({ } }); -instance.web.DropMisordered = instance.web.Class.extend(/** @lends openerp.web.DropMisordered# */{ +instance.web.DropMisordered = instance.web.Class.extend({ /** * @constructs instance.web.DropMisordered * @extends instance.web.Class diff --git a/addons/web/static/src/js/data_export.js b/addons/web/static/src/js/data_export.js index ebe82ddb4b4..29aea6362f7 100644 --- a/addons/web/static/src/js/data_export.js +++ b/addons/web/static/src/js/data_export.js @@ -21,31 +21,31 @@ instance.web.DataExport = instance.web.Dialog.extend({ ], close: function(event, ui){ self.close();} }); - self.$element.removeClass('ui-dialog-content ui-widget-content'); - self.$element.find('#add_field').click(function() { + self.$el.removeClass('ui-dialog-content ui-widget-content'); + self.$el.find('#add_field').click(function() { if ($('#field-tree-structure tr.ui-selected')) { - var fld = self.$element.find('#field-tree-structure tr.ui-selected').find('a'); + var fld = self.$el.find('#field-tree-structure tr.ui-selected').find('a'); for (var i = 0; i < fld.length; i++) { var id = $(fld[i]).attr('id').split('-')[1]; var string = $(fld[i]).attr('string'); self.add_field(id, string); } - self.$element.find('#field-tree-structure tr').removeClass('ui-selected'); + self.$el.find('#field-tree-structure tr').removeClass('ui-selected'); } }); - self.$element.find('#remove_field').click(function() { - self.$element.find('#fields_list option:selected').remove(); + self.$el.find('#remove_field').click(function() { + self.$el.find('#fields_list option:selected').remove(); }); - self.$element.find('#remove_all_field').click(function() { - self.$element.find('#fields_list').empty(); + self.$el.find('#remove_all_field').click(function() { + self.$el.find('#fields_list').empty(); }); - this.$element.find('#export_new_list').click(this.on_show_save_list); + this.$el.find('#export_new_list').click(this.on_show_save_list); var got_fields = new $.Deferred(); - this.$element.find('#import_compat').change(function() { - self.$element.find('#fields_list').empty(); - self.$element.find('#field-tree-structure').remove(); - var import_comp = self.$element.find("#import_compat").val(); + this.$el.find('#import_compat').change(function() { + self.$el.find('#fields_list').empty(); + self.$el.find('#field-tree-structure').remove(); + var import_comp = self.$el.find("#import_compat").val(); self.rpc("/web/export/get_fields", { model: self.dataset.model, import_compat: Boolean(import_comp) @@ -61,7 +61,7 @@ instance.web.DataExport = instance.web.Dialog.extend({ this.show_exports_list()); }, do_setup_export_formats: function (formats) { - var $fmts = this.$element.find('#export_format'); + var $fmts = this.$el.find('#export_format'); _(formats).each(function (format) { var opt = new Option(format.label, format.tag); if (format.error) { @@ -76,8 +76,8 @@ instance.web.DataExport = instance.web.Dialog.extend({ }, show_exports_list: function() { var self = this; - if (self.$element.find('#saved_export_list').is(':hidden')) { - self.$element.find('#ExistsExportList').show(); + if (self.$el.find('#saved_export_list').is(':hidden')) { + self.$el.find('#ExistsExportList').show(); return; } return this.exports.read_slice(['name'], { @@ -86,35 +86,35 @@ instance.web.DataExport = instance.web.Dialog.extend({ if (!export_list.length) { return; } - self.$element.find('#ExistsExportList').append(QWeb.render('Exists.ExportList', {'existing_exports': export_list})); - self.$element.find('#saved_export_list').change(function() { - self.$element.find('#fields_list option').remove(); - var export_id = self.$element.find('#saved_export_list option:selected').val(); + self.$el.find('#ExistsExportList').append(QWeb.render('Exists.ExportList', {'existing_exports': export_list})); + self.$el.find('#saved_export_list').change(function() { + self.$el.find('#fields_list option').remove(); + var export_id = self.$el.find('#saved_export_list option:selected').val(); if (export_id) { self.rpc('/web/export/namelist', {'model': self.dataset.model, export_id: parseInt(export_id)}, self.do_load_export_field); } }); - self.$element.find('#delete_export_list').click(function() { - var select_exp = self.$element.find('#saved_export_list option:selected'); + self.$el.find('#delete_export_list').click(function() { + var select_exp = self.$el.find('#saved_export_list option:selected'); if (select_exp.val()) { self.exports.unlink([parseInt(select_exp.val(), 10)]); select_exp.remove(); - if (self.$element.find('#saved_export_list option').length <= 1) { - self.$element.find('#ExistsExportList').hide(); + if (self.$el.find('#saved_export_list option').length <= 1) { + self.$el.find('#ExistsExportList').hide(); } } }); }); }, do_load_export_field: function(field_list) { - var export_node = this.$element.find("#fields_list"); + var export_node = this.$el.find("#fields_list"); _(field_list).each(function (field) { export_node.append(new Option(field.label, field.name)); }); }, on_show_save_list: function() { var self = this; - var current_node = self.$element.find("#savenewlist"); + var current_node = self.$el.find("#savenewlist"); if (!(current_node.find("label")).length) { current_node.append(QWeb.render('ExportNewList')); current_node.find("#add_export_list").click(function() { @@ -150,14 +150,14 @@ instance.web.DataExport = instance.web.Dialog.extend({ if (!export_list_id.result) { return; } - self.$element.find("#saved_export_list").append( + self.$el.find("#saved_export_list").append( new Option(value, export_list_id.result)); - if (self.$element.find("#saved_export_list").is(":hidden")) { + if (self.$el.find("#saved_export_list").is(":hidden")) { self.show_exports_list(); } }); this.on_show_save_list(); - this.$element.find("#fields_list option").remove(); + this.$el.find("#fields_list option").remove(); }, on_click: function(id, record) { var self = this; @@ -173,7 +173,7 @@ instance.web.DataExport = instance.web.Dialog.extend({ } if (!record.loaded) { - var import_comp = self.$element.find("#import_compat").val(); + var import_comp = self.$el.find("#import_compat").val(); self.rpc("/web/export/get_fields", { model: model, prefix: prefix, @@ -191,44 +191,44 @@ instance.web.DataExport = instance.web.Dialog.extend({ }, on_show_data: function(result, after) { var self = this; - var imp_cmpt = Boolean(self.$element.find("#import_compat").val()); + var imp_cmpt = Boolean(self.$el.find("#import_compat").val()); if (after) { - var current_tr = self.$element.find("tr[id='treerow-" + after + "']"); + var current_tr = self.$el.find("tr[id='treerow-" + after + "']"); current_tr.addClass('open'); current_tr.find('img').attr('src','/web/static/src/img/collapse.gif'); current_tr.after(QWeb.render('ExportTreeView-Secondary.children', {'fields': result})); } else { - self.$element.find('#left_field_panel').append(QWeb.render('ExportTreeView-Secondary', {'fields': result})); + self.$el.find('#left_field_panel').append(QWeb.render('ExportTreeView-Secondary', {'fields': result})); } _.each(result, function(record) { self.records[record.id] = record.value; if (record.required) { - var required_fld = self.$element.find("tr[id='treerow-" + record.id + "']").find('#tree-column'); + var required_fld = self.$el.find("tr[id='treerow-" + record.id + "']").find('#tree-column'); required_fld.addClass("oe_export_requiredfield"); } - self.$element.find("img[id='parentimg-" + record.id +"']").click(function() { + self.$el.find("img[id='parentimg-" + record.id +"']").click(function() { self.on_click(this.id, record); }); - self.$element.find("tr[id='treerow-" + record.id + "']").click(function(e) { + self.$el.find("tr[id='treerow-" + record.id + "']").click(function(e) { if (e.shiftKey) { var frst_click, scnd_click = ''; if (self.row_index == 0) { self.row_index = this.rowIndex; - frst_click = self.$element.find("tr[id^='treerow-']")[self.row_index-1]; + frst_click = self.$el.find("tr[id^='treerow-']")[self.row_index-1]; $(frst_click).addClass("ui-selected"); } else { if (this.rowIndex >=self.row_index) { for (var i = (self.row_index-1); i < this.rowIndex; i++) { - scnd_click = self.$element.find("tr[id^='treerow-']")[i]; + scnd_click = self.$el.find("tr[id^='treerow-']")[i]; if (!$(scnd_click).find('#tree-column').hasClass("oe_export_readonlyfield")) { $(scnd_click).addClass("ui-selected"); } } } else { for (var i = (self.row_index-1); i >= (this.rowIndex-1); i--) { - scnd_click = self.$element.find("tr[id^='treerow-']")[i]; + scnd_click = self.$el.find("tr[id^='treerow-']")[i]; if (!$(scnd_click).find('#tree-column').hasClass("oe_export_readonlyfield")) { $(scnd_click).addClass("ui-selected"); } @@ -238,10 +238,10 @@ instance.web.DataExport = instance.web.Dialog.extend({ } self.row_index = this.rowIndex; - self.$element.find("tr[id='treerow-" + record.id + "']").keyup(function() { + self.$el.find("tr[id='treerow-" + record.id + "']").keyup(function() { self.row_index = 0; }); - var o2m_selection = self.$element.find("tr[id='treerow-" + record.id + "']").find('#tree-column'); + var o2m_selection = self.$el.find("tr[id='treerow-" + record.id + "']").find('#tree-column'); if ($(o2m_selection).hasClass("oe_export_readonlyfield")) { return false; } @@ -252,14 +252,14 @@ instance.web.DataExport = instance.web.Dialog.extend({ $(this).addClass('ui-selected').find('a').focus(); } } else if (!e.shiftKey) { - self.$element.find("tr.ui-selected") + self.$el.find("tr.ui-selected") .removeClass("ui-selected").find('a').blur(); $(this).addClass("ui-selected").find('a').focus(); } return false; }); - self.$element.find("tr[id='treerow-" + record.id + "']").keydown(function(e) { + self.$el.find("tr[id='treerow-" + record.id + "']").keydown(function(e) { var keyCode = e.keyCode || e.which; var arrow = {left: 37, up: 38, right: 39, down: 40 }; switch (keyCode) { @@ -297,19 +297,19 @@ instance.web.DataExport = instance.web.Dialog.extend({ break; } }); - self.$element.find("tr[id='treerow-" + record.id + "']").dblclick(function() { - var $o2m_selection = self.$element.find("tr[id^='treerow-" + record.id + "']").find('#tree-column'); + self.$el.find("tr[id='treerow-" + record.id + "']").dblclick(function() { + var $o2m_selection = self.$el.find("tr[id^='treerow-" + record.id + "']").find('#tree-column'); if (!$o2m_selection.hasClass("oe_export_readonlyfield")) { self.add_field(record.id, $(this).find("a").attr("string")); } }); }); - self.$element.find('#fields_list').mouseover(function(event) { + self.$el.find('#fields_list').mouseover(function(event) { if (event.relatedTarget) { if (event.relatedTarget.attributes['id'] && event.relatedTarget.attributes['string']) { var field_id = event.relatedTarget.attributes["id"]["value"]; if (field_id && field_id.split("-")[0] === 'export') { - if (!self.$element.find("tr[id='treerow-" + field_id.split("-")[1] + "']").find('#tree-column').hasClass("oe_export_readonlyfield")) { + if (!self.$el.find("tr[id='treerow-" + field_id.split("-")[1] + "']").find('#tree-column').hasClass("oe_export_readonlyfield")) { self.add_field(field_id.split("-")[1], event.relatedTarget.attributes["string"]["value"]); } } @@ -319,7 +319,7 @@ instance.web.DataExport = instance.web.Dialog.extend({ }, showcontent: function(id) { // show & hide the contents - var $this = this.$element.find("tr[id='treerow-" + id + "']"); + var $this = this.$el.find("tr[id='treerow-" + id + "']"); var is_open = $this.hasClass('open'); $this.toggleClass('open'); @@ -329,7 +329,7 @@ instance.web.DataExport = instance.web.Dialog.extend({ } else { first_child.attr('src', '/web/static/src/img/collapse.gif'); } - var child_field = this.$element.find("tr[id^='treerow-" + id +"/']"); + var child_field = this.$el.find("tr[id^='treerow-" + id +"/']"); var child_len = (id.split("/")).length + 1; for (var i = 0; i < child_field.length; i++) { var $child = $(child_field[i]); @@ -345,15 +345,15 @@ instance.web.DataExport = instance.web.Dialog.extend({ } }, add_field: function(field_id, string) { - var field_list = this.$element.find('#fields_list'); - if (this.$element.find("#fields_list option[value='" + field_id + "']") - && !this.$element.find("#fields_list option[value='" + field_id + "']").length) { + var field_list = this.$el.find('#fields_list'); + if (this.$el.find("#fields_list option[value='" + field_id + "']") + && !this.$el.find("#fields_list option[value='" + field_id + "']").length) { field_list.append(new Option(string, field_id)); } }, get_fields: function() { var export_field = []; - this.$element.find("#fields_list option").each(function() { + this.$el.find("#fields_list option").each(function() { export_field.push($(this).val()); }); if (!export_field.length) { @@ -363,7 +363,7 @@ instance.web.DataExport = instance.web.Dialog.extend({ }, on_click_export_data: function() { var self = this; - var exported_fields = this.$element.find('#fields_list option').map(function () { + var exported_fields = this.$el.find('#fields_list option').map(function () { // DOM property is textContent, but IE8 only knows innerText return {name: self.records[this.value] || this.value, label: this.textContent || this.innerText}; @@ -375,8 +375,8 @@ instance.web.DataExport = instance.web.Dialog.extend({ } exported_fields.unshift({name: 'id', label: 'External ID'}); - var export_format = this.$element.find("#export_format").val(); - $.blockUI(); + var export_format = this.$el.find("#export_format").val(); + instance.web.blockUI(); this.session.get_file({ url: '/web/export/' + export_format, data: {data: JSON.stringify({ @@ -385,13 +385,13 @@ instance.web.DataExport = instance.web.Dialog.extend({ ids: this.dataset.ids, domain: this.dataset.domain, import_compat: Boolean( - this.$element.find("#import_compat").val()) + this.$el.find("#import_compat").val()) })}, - complete: $.unblockUI + complete: instance.web.unblockUI }); }, close: function() { - this.$element.remove(); + this.$el.remove(); this._super(); } }); diff --git a/addons/web/static/src/js/data_import.js b/addons/web/static/src/js/data_import.js deleted file mode 100644 index 30f9323f723..00000000000 --- a/addons/web/static/src/js/data_import.js +++ /dev/null @@ -1,366 +0,0 @@ -openerp.web.data_import = function(instance) { -var QWeb = instance.web.qweb, - _t = instance.web._t; -/** - * Safari does not deal well at all with raw JSON data being returned. As a - * result, we're going to cheat by using a pseudo-jsonp: instead of getting - * JSON data in the iframe, we're getting a ``script`` tag which consists of a - * function call and the returned data (the json dump). - * - * The function is an auto-generated name bound to ``window``, which calls - * back into the callback provided here. - * - * @param {Object} form the form element (DOM or jQuery) to use in the call - * @param {Object} attributes jquery.form attributes object - * @param {Function} callback function to call with the returned data - */ -function jsonp(form, attributes, callback) { - attributes = attributes || {}; - var options = {jsonp: _.uniqueId('import_callback_')}; - window[options.jsonp] = function () { - delete window[options.jsonp]; - callback.apply(null, arguments); - }; - if ('data' in attributes) { - _.extend(attributes.data, options); - } else { - _.extend(attributes, {data: options}); - } - $(form).ajaxSubmit(attributes); -} - -instance.web.DataImport = instance.web.Dialog.extend({ - template: 'ImportDataView', - dialog_title: {toString: function () { return _t("Import Data"); }}, - init: function(parent, dataset){ - var self = this; - this._super(parent, {}); - this.model = parent.model; - this.fields = []; - this.all_fields = []; - this.fields_with_defaults = []; - this.required_fields = null; - - var convert_fields = function (root, prefix) { - prefix = prefix || ''; - _(root.fields).each(function (f) { - self.all_fields.push(prefix + f.name); - if (f.fields) { - convert_fields(f, prefix + f.id + '/'); - } - }); - }; - this.ready = $.Deferred.queue().then(function () { - self.required_fields = _(self.fields).chain() - .filter(function (field) { - return field.required && - !_.include(self.fields_with_defaults, field.id); }) - .pluck('name') - .value(); - convert_fields(self); - self.all_fields.sort(); - }); - }, - start: function() { - var self = this; - this._super(); - this.open({ - buttons: [ - {text: _t("Close"), click: function() { self.destroy(); }}, - {text: _t("Import File"), click: function() { self.do_import(); }, 'class': 'oe-dialog-import-button'} - ], - close: function(event, ui) { - self.destroy(); - } - }); - this.toggle_import_button(false); - this.$element.find('#csvfile').change(this.on_autodetect_data); - this.$element.find('fieldset').change(this.on_autodetect_data); - this.$element.delegate('fieldset legend', 'click', function() { - $(this).parent().toggleClass('oe-closed'); - }); - this.ready.push(new instance.web.DataSet(this, this.model).call( - 'fields_get', [], function (fields) { - self.graft_fields(fields); - self.ready.push(new instance.web.DataSet(self, self.model) - .default_get(_.pluck(self.fields, 'id')).then(function (fields) { - _.each(fields, function(val, key) { - if (val) { - self.fields_with_defaults.push(key); - } - }); - }) - ) - })); - }, - graft_fields: function (fields, parent, level) { - parent = parent || this; - level = level || 0; - - var self = this; - if (level === 0) { - parent.fields.push({ - id: 'id', - name: 'id', - string: _t('External ID'), - required: false - }); - } - _(fields).each(function (field, field_name) { - // Ignore spec for id field - // Don't import function fields (function and related) - if (field_name === 'id') { - return; - } - // Skip if there's no state which could disable @readonly, - // if a field is ever always readonly we can't import it - if (field.readonly) { - // no states at all - if (_.isEmpty(field.states)) { return; } - // no state altering @readonly - if (!_.any(field.states, function (modifiers) { - return _(modifiers).chain().pluck(0).contains('readonly').value(); - })) { return; } - } - - var f = { - id: field_name, - name: field_name, - string: field.string, - required: field.required - }; - - switch (field.type) { - case 'many2many': - case 'many2one': - f.name += '/id'; - break; - case 'one2many': - f.name += '/id'; - f.fields = []; - // only fetch sub-fields to a depth of 2 levels - if (level < 2) { - self.ready.push(new instance.web.DataSet(self, field.relation).call( - 'fields_get', [], function (fields) { - self.graft_fields(fields, f, level+1); - })); - } - break; - } - parent.fields.push(f); - }); - }, - toggle_import_button: function (newstate) { - instance.web.dialog(this.$element, 'widget') - .find('.oe-dialog-import-button') - .button('option', 'disabled', !newstate); - }, - do_import: function() { - if(!this.$element.find('#csvfile').val()) { return; } - var lines_to_skip = parseInt(this.$element.find('#csv_skip').val(), 10); - var with_headers = this.$element.find('#file_has_headers').prop('checked'); - if (!lines_to_skip && with_headers) { - lines_to_skip = 1; - } - var indices = [], fields = []; - this.$element.find(".sel_fields").each(function(index, element) { - var val = element.value; - if (!val) { - return; - } - indices.push(index); - fields.push(val); - }); - - jsonp(this.$element.find('#import_data'), { - url: '/web/import/import_data', - data: { - model: this.model, - meta: JSON.stringify({ - skip: lines_to_skip, - indices: indices, - fields: fields - }) - } - }, this.on_import_results); - }, - on_autodetect_data: function() { - if(!this.$element.find('#csvfile').val()) { return; } - jsonp(this.$element.find('#import_data'), { - url: '/web/import/detect_data' - }, this.on_import_results); - }, - on_import_results: function(results) { - this.$element.find('#result').empty(); - var headers, result_node = this.$element.find("#result"); - - if (results['error']) { - result_node.append(QWeb.render('ImportView.error', { - 'error': results['error']})); - this.$element.find('fieldset').removeClass('oe-closed'); - return; - } - if (results['success']) { - if (this.getParent().getParent().active_view == "list") { - this.getParent().reload_content(); - } - this.destroy(); - return; - } - - if (results['records']) { - var lines_to_skip = parseInt(this.$element.find('#csv_skip').val(), 10), - with_headers = this.$element.find('#file_has_headers').prop('checked'); - headers = with_headers ? results.records[0] : null; - - result_node.append(QWeb.render('ImportView.result', { - 'headers': headers, - 'records': lines_to_skip ? results.records.slice(lines_to_skip) - : with_headers ? results.records.slice(1) - : results.records - })); - this.$element.find('fieldset').addClass('oe-closed'); - } - this.$element.find('form').removeClass('oe-import-no-result'); - - this.$element.delegate('.oe-m2o-drop-down-button', 'click', function () { - $(this).prev('input').focus(); - }); - - var self = this; - this.ready.then(function () { - var $fields = self.$element.find('.sel_fields').bind('blur', function () { - if (this.value && !_(self.all_fields).contains(this.value)) { - this.value = ''; - } - }).autocomplete({ - minLength: 0, - source: self.all_fields, - change: self.on_check_field_values - }).focus(function () { - $(this).autocomplete('search'); - }); - // Column auto-detection - _(headers).each(function (header, index) { - var field_name = self.match_column_to_field(header); - if (field_name) { - $fields.eq(index).val(field_name); - } - }); - self.on_check_field_values(); - }); - }, - /** - * Returns the name of the field (nested) matching the provided column name - * - * @param {String} name column name to look for - * @param {Array} [fields] fields to look into for the provided name - * @returns {String|undefined} - */ - match_column_to_field: function (name, fields) { - fields = fields || this.fields; - var f; - f = _(fields).detect(function (field) { - // TODO: levenshtein between header and field.string - return field.name === name - || field.string.toLowerCase() === name.toLowerCase(); - }); - if (f) { return f.name; } - - // if ``name`` is a path (o2m), we need to recurse through its .fields - var index = name.indexOf('/'); - if (index === -1) { return undefined; } - // Get the first path section, try to find the matching field - var column_name = name.substring(0, index); - f = _(fields).detect(function (field) { - // field.name for o2m is $foo/id, so we want to match on id - return field.id === column_name - || field.string.toLowerCase() === column_name.toLowerCase() - }); - if (!f) { return undefined; } - - // if we found a matching field for the first path section, recurse in - // its own .fields to try and get the rest of the path matched - var rest = this.match_column_to_field( - name.substring(index+1), f.fields); - if (!rest) { return undefined; } - return f.id + '/' + rest; - }, - /** - * Looks through all the field selections, and tries to find if two - * (or more) columns were matched to the same model field. - * - * Returns a map of the multiply-mapped fields to an array of offending - * columns (not actually columns, but the inputs containing the same field - * names). - * - * Also has the side-effect of marking the discovered inputs with the class - * ``duplicate_fld``. - * - * @returns {Object<String, Array<String>>} map of duplicate field matches to same-valued inputs - */ - find_duplicate_fields: function() { - // Maps values to DOM nodes, in order to discover duplicates - var values = {}, duplicates = {}; - this.$element.find(".sel_fields").each(function(index, element) { - var value = element.value; - var $element = $(element).removeClass('duplicate_fld'); - if (!value) { return; } - - if (!(value in values)) { - values[value] = element; - } else { - var same_valued_field = values[value]; - if (value in duplicates) { - duplicates[value].push(element); - } else { - duplicates[value] = [same_valued_field, element]; - } - $element.add(same_valued_field).addClass('duplicate_fld'); - } - }); - return duplicates; - }, - on_check_field_values: function () { - this.$element.find("#message, #msg").remove(); - - var required_valid = this.check_required(); - - var duplicates = this.find_duplicate_fields(); - if (_.isEmpty(duplicates)) { - this.toggle_import_button(required_valid); - } else { - var $err = $('<div id="msg" style="color: red;">Destination fields should only be selected once, some fields are selected more than once:</div>').insertBefore(this.$element.find('#result')); - var $dupes = $('<dl>').appendTo($err); - _(duplicates).each(function(elements, value) { - $('<dt>').text(value).appendTo($dupes); - _(elements).each(function(element) { - var cell = $(element).closest('td'); - $('<dd>').text(cell.parent().children().index(cell)).appendTo($dupes); - }); - }); - this.toggle_import_button(false); - } - - }, - check_required: function() { - if (!this.required_fields.length) { return true; } - - var selected_fields = _(this.$element.find('.sel_fields').get()).chain() - .pluck('value') - .compact() - .value(); - - var missing_fields = _.difference(this.required_fields, selected_fields); - if (missing_fields.length) { - this.$element.find("#result").before('<div id="message" style="color:red">*Required Fields are not selected : ' + missing_fields + '.</div>'); - return false; - } - return true; - }, - destroy: function() { - this.$element.remove(); - this._super(); - } -}); -}; diff --git a/addons/web/static/src/js/formats.js b/addons/web/static/src/js/formats.js index 0f1c71643be..3e5e6fecd5e 100644 --- a/addons/web/static/src/js/formats.js +++ b/addons/web/static/src/js/formats.js @@ -118,7 +118,9 @@ instance.web.format_value = function (value, descriptor, value_if_empty) { return instance.web.insert_thousand_seps( _.str.sprintf('%d', value)); case 'float': - var precision = descriptor.digits ? descriptor.digits[1] : 2; + var digits = descriptor.digits ? descriptor.digits : [69,2]; + digits = typeof digits === "string" ? py.eval(digits) : digits; + var precision = digits[1]; var formatted = _.str.sprintf('%.' + precision + 'f', value).split('.'); formatted[0] = instance.web.insert_thousand_seps(formatted[0]); return formatted.join(l10n.decimal_point); @@ -136,6 +138,9 @@ instance.web.format_value = function (value, descriptor, value_if_empty) { return value[1]; case 'one2many': case 'many2many': + if (typeof value === 'string') { + return value; + } return _.str.sprintf(_t("(%d records)"), value.length); case 'datetime': if (typeof(value) == "string") @@ -269,82 +274,4 @@ instance.web.auto_date_to_str = function(value, type) { } }; -/** - * Formats a provided cell based on its field type. Most of the field types - * return a correctly formatted value, but some tags and fields are - * special-cased in their handling: - * - * * buttons will return an actual ``<button>`` tag with a bunch of error handling - * - * * boolean fields will return a checkbox input, potentially disabled - * - * * binary fields will return a link to download the binary data as a file - * - * @param {Object} row_data record whose values should be displayed in the cell - * @param {Object} column column descriptor - * @param {"button"|"field"} column.tag base control type - * @param {String} column.type widget type for a field control - * @param {String} [column.string] button label - * @param {String} [column.icon] button icon - * @param {Object} [options] - * @param {String} [options.value_if_empty=''] what to display if the field's value is ``false`` - * @param {Boolean} [options.process_modifiers=true] should the modifiers be computed ? - * @param {String} [options.model] current record's model - * @param {Number} [options.id] current record's id - * - */ -instance.web.format_cell = function (row_data, column, options) { - options = options || {}; - var attrs = {}; - if (options.process_modifiers !== false) { - attrs = column.modifiers_for(row_data); - } - if (attrs.invisible) { return ''; } - - if (column.tag === 'button') { - return _.template('<button type="button" title="<%-title%>" <%=additional_attributes%> >' + - '<img src="<%-prefix%>/web/static/src/img/icons/<%-icon%>.png" alt="<%-alt%>"/>' + - '</button>', { - title: column.string || '', - additional_attributes: isNaN(row_data["id"].value) && instance.web.BufferedDataSet.virtual_id_regex.test(row_data["id"].value) ? - 'disabled="disabled" class="oe-listview-button-disabled"' : '', - prefix: instance.connection.prefix, - icon: column.icon, - alt: column.string || '' - }); - } - if (!row_data[column.id]) { - return options.value_if_empty === undefined ? '' : options.value_if_empty; - } - - switch (column.widget || column.type) { - case "boolean": - return _.str.sprintf('<input type="checkbox" %s disabled="disabled"/>', - row_data[column.id].value ? 'checked="checked"' : ''); - case "binary": - var text = _t("Download"), - download_url = _.str.sprintf('/web/binary/saveas?session_id=%s&model=%s&field=%s&id=%d', instance.connection.session_id, options.model, column.id, options.id); - if (column.filename) { - download_url += '&filename_field=' + column.filename; - if (row_data[column.filename]) { - text = _.str.sprintf(_t("Download \"%s\""), instance.web.format_value( - row_data[column.filename].value, {type: 'char'})); - } - } - return _.template('<a href="<%-href%>"><%-text%></a> (%<-size%>)', { - text: text, - href: download_url, - size: row_data[column.id].value - }); - case 'progressbar': - return _.template( - '<progress value="<%-value%>" max="100"><%-value%>%</progress>', { - value: _.str.sprintf("%.0f", row_data[column.id].value || 0) - }); - } - - return _.escape(instance.web.format_value( - row_data[column.id].value, column, options.value_if_empty)); -} - }; diff --git a/addons/web/static/src/js/search.js b/addons/web/static/src/js/search.js index f9e737471ae..01bc0c76c87 100644 --- a/addons/web/static/src/js/search.js +++ b/addons/web/static/src/js/search.js @@ -126,21 +126,21 @@ my.InputView = instance.web.Widget.extend({ template: 'SearchView.InputView', start: function () { var p = this._super.apply(this, arguments); - this.$element.on('focus', this.proxy('onFocus')); - this.$element.on('blur', this.proxy('onBlur')); - this.$element.on('keydown', this.proxy('onKeydown')); + this.$el.on('focus', this.proxy('onFocus')); + this.$el.on('blur', this.proxy('onBlur')); + this.$el.on('keydown', this.proxy('onKeydown')); return p; }, onFocus: function () { this.trigger('focused', this); }, onBlur: function () { - this.$element.text(''); + this.$el.text(''); this.trigger('blurred', this); }, getSelection: function () { // get Text node - var root = this.$element[0].childNodes[0]; + var root = this.$el[0].childNodes[0]; if (!root || !root.textContent) { // if input does not have a child node, or the child node is an // empty string, then the selection can only be (0, 0) @@ -202,7 +202,7 @@ my.InputView = instance.web.Widget.extend({ break; case $.ui.keyCode.RIGHT: sel = this.getSelection(); - var len = this.$element.text().length; + var len = this.$el.text().length; if (sel.start !== len || sel.start !== sel.end) { e.stopPropagation(); } @@ -223,17 +223,17 @@ my.FacetView = instance.web.Widget.extend({ }, start: function () { var self = this; - this.$element.on('focus', function () { self.trigger('focused', self); }); - this.$element.on('blur', function () { self.trigger('blurred', self); }); - this.$element.on('click', function (e) { + this.$el.on('focus', function () { self.trigger('focused', self); }); + this.$el.on('blur', function () { self.trigger('blurred', self); }); + this.$el.on('click', function (e) { if ($(e.target).is('.oe_facet_remove')) { self.model.destroy(); return false; } - self.$element.focus(); + self.$el.focus(); e.stopPropagation(); }); - this.$element.on('keydown', function (e) { + this.$el.on('keydown', function (e) { var keys = $.ui.keyCode; switch (e.which) { case keys.BACKSPACE: @@ -242,7 +242,7 @@ my.FacetView = instance.web.Widget.extend({ return false; } }); - var $e = self.$element.find('> span:last-child'); + var $e = self.$el.find('> span:last-child'); var q = $.when(this._super()); return q.pipe(function () { var values = self.model.values.map(function (value) { @@ -253,7 +253,7 @@ my.FacetView = instance.web.Widget.extend({ }); }, model_changed: function () { - this.$element.text(this.$element.text() + '*'); + this.$el.text(this.$el.text() + '*'); } }); my.FacetValueView = instance.web.Widget.extend({ @@ -268,7 +268,7 @@ my.FacetValueView = instance.web.Widget.extend({ this._super(); }, model_changed: function () { - this.$element.text(this.$element.text() + '*'); + this.$el.text(this.$el.text() + '*'); } }); @@ -313,7 +313,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea .on('add change reset remove', this.proxy('renderFacets')); if (this.hidden) { - this.$element.hide(); + this.$el.hide(); } if (this.headless) { this.ready.resolve(); @@ -330,7 +330,13 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea }); } - this.$element.on('keydown', + // Launch a search on clicking the oe_searchview_search button + this.$el.on('click', 'button.oe_searchview_search', function (e) { + e.stopImmediatePropagation(); + self.do_search(); + }); + + this.$el.on('keydown', '.oe_searchview_input, .oe_searchview_facet', function (e) { switch(e.which) { case $.ui.keyCode.LEFT: @@ -344,26 +350,31 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea } }); - this.$element.on('click', '.oe_searchview_clear', function (e) { + this.$el.on('click', '.oe_searchview_clear', function (e) { e.stopImmediatePropagation(); self.query.reset(); }); - this.$element.on('click', '.oe_searchview_unfold_drawer', function (e) { + this.$el.on('click', '.oe_searchview_unfold_drawer', function (e) { e.stopImmediatePropagation(); - self.$element.toggleClass('oe_searchview_open_drawer'); + self.$el.toggleClass('oe_searchview_open_drawer'); + }); + instance.web.bus.on('click', this, function(ev) { + if ($(ev.target).parents('.oe_searchview').length === 0) { + self.$el.removeClass('oe_searchview_open_drawer'); + } }); // Focus last input if the view itself is clicked (empty section of // facets element) - this.$element.on('click', function (e) { - if (e.target === self.$element.find('.oe_searchview_facets')[0]) { - self.$element.find('.oe_searchview_input:last').focus(); + this.$el.on('click', function (e) { + if (e.target === self.$el.find('.oe_searchview_facets')[0]) { + self.$el.find('.oe_searchview_input:last').focus(); } }); // when the completion list opens/refreshes, automatically select the // first completion item so if the user just hits [RETURN] or [TAB] it // automatically selects it - this.$element.on('autocompleteopen', function () { - var menu = self.$element.data('autocomplete').menu; + this.$el.on('autocompleteopen', function () { + var menu = self.$el.data('autocomplete').menu; menu.activate( $.Event({ type: "mouseenter" }), menu.element.children().first()); @@ -372,15 +383,15 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea return $.when(p, this.ready); }, show: function () { - this.$element.show(); + this.$el.show(); }, hide: function () { - this.$element.hide(); + this.$el.hide(); }, subviewForRoot: function (subview_root) { return _(this.input_subviews).detect(function (subview) { - return subview.$element[0] === subview_root; + return subview.$el[0] === subview_root; }); }, siblingSubview: function (subview, direction, wrap_around) { @@ -395,12 +406,12 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea focusPreceding: function (subview_root) { return this.siblingSubview( this.subviewForRoot(subview_root), -1, true) - .$element.focus(); + .$el.focus(); }, focusFollowing: function (subview_root) { return this.siblingSubview( this.subviewForRoot(subview_root), +1, true) - .$element.focus(); + .$el.focus(); }, /** @@ -419,19 +430,19 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea // autocomplete only correctly handles being initialized on the actual // editable element (and only an element with a @value in 1.8 e.g. - // input or textarea), cheat by setting val() on $element - this.$element.on('keydown', function () { + // input or textarea), cheat by setting val() on $el + this.$el.on('keydown', function () { // keydown is triggered *before* the element's value is set, so // delay this. Pray that setTimeout are executed in FIFO (if they // have the same delay) as autocomplete uses the exact same trick. // FIXME: brittle as fuck setTimeout(function () { - self.$element.val(self.currentInputValue()); + self.$el.val(self.currentInputValue()); }, 0); }); - this.$element.autocomplete({ + this.$el.autocomplete({ source: this.proxy('complete_global_search'), select: this.proxy('select_completion'), focus: function (e) { e.preventDefault(); }, @@ -468,7 +479,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea * div[contenteditable].oe_searchview_input) */ currentInputValue: function () { - return this.$element.find('div.oe_searchview_input:focus').text(); + return this.$el.find('div.oe_searchview_input:focus').text(); }, /** * Provide auto-completion result for req.term (an array to `resp`) @@ -498,14 +509,23 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea var input_index = _(this.input_subviews).indexOf( this.subviewForRoot( - this.$element.find('div.oe_searchview_input:focus')[0])); + this.$el.find('div.oe_searchview_input:focus')[0])); this.query.add(ui.item.facet, {at: input_index / 2}); }, childFocused: function () { - this.$element.addClass('oe_focused'); + this.$el.addClass('oe_focused'); }, childBlurred: function () { - this.$element.removeClass('oe_focused'); + var val = this.$el.val(); + this.$el.val(''); + var complete = this.$el.data('autocomplete'); + if ((val && complete.term === undefined) || complete.previous !== undefined) { + throw new Error("new jquery.ui version altering implementation" + + " details relied on"); + } + delete complete.term; + this.$el.removeClass('oe_focused') + .trigger('blur'); }, /** * @@ -518,7 +538,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea // _2: undefined if event=change, otherwise model var self = this; var started = []; - var $e = this.$element.find('div.oe_searchview_facets'); + var $e = this.$el.find('div.oe_searchview_facets'); _.invoke(this.input_subviews, 'destroy'); this.input_subviews = []; @@ -549,7 +569,7 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea input_to_focus = self.input_subviews[(options.at + 1) * 2]; } - input_to_focus.$element.focus(); + input_to_focus.$el.focus(); }); }, @@ -615,6 +635,16 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea return null; } }, + + add_common_inputs: function() { + // add Filters to this.inputs, need view.controls filled + (new instance.web.search.Filters(this)); + // add custom filters to this.inputs + (new instance.web.search.CustomFilters(this)); + // add Advanced to this.inputs + (new instance.web.search.Advanced(this)); + }, + on_loaded: function(data) { var self = this; this.fields_view = data.fields_view; @@ -629,18 +659,13 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea data.fields_view['arch'].children, data.fields_view.fields); - // add Filters to this.inputs, need view.controls filled - (new instance.web.search.Filters(this)); - // add custom filters to this.inputs - (new instance.web.search.CustomFilters(this)); - // add Advanced to this.inputs - (new instance.web.search.Advanced(this)); + this.add_common_inputs(); // build drawer var drawer_started = $.when.apply( null, _(this.select_for_drawer()).invoke( - 'appendTo', this.$element.find('.oe_searchview_drawer'))); - + 'appendTo', this.$el.find('.oe_searchview_drawer'))); + // load defaults var defaults_fetched = $.when.apply(null, _(this.inputs).invoke( 'facet_for_defaults', this.defaults)).then(function () { @@ -655,15 +680,12 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea */ on_filters_management: function(e) { var self = this; - var select = this.$element.find(".oe_search-view-filters-management"); + var select = this.$el.find(".oe_search-view-filters-management"); var val = select.val(); switch(val) { case 'advanced_filter': this.extended_search.on_activate(); break; - case 'add_to_dashboard': - this.on_add_to_dashboard(); - break; case '': this.do_clear(); } @@ -692,53 +714,6 @@ instance.web.SearchView = instance.web.Widget.extend(/** @lends instance.web.Sea select.val(''); } }, - on_add_to_dashboard: function() { - this.$element.find(".oe_search-view-filters-management")[0].selectedIndex = 0; - var self = this, - menu = instance.webclient.menu, - $dialog = $(QWeb.render("SearchView.add_to_dashboard", { - dashboards : menu.data.data.children, - selected_menu_id : menu.$element.find('a.active').data('menu') - })); - $dialog.find('input').val(this.fields_view.name); - instance.web.dialog($dialog, { - modal: true, - title: _t("Add to Dashboard"), - buttons: [ - {text: _t("Cancel"), click: function() { - $(this).dialog("close"); - }}, - {text: _t("OK"), click: function() { - $(this).dialog("close"); - var menu_id = $(this).find("select").val(), - title = $(this).find("input").val(), - data = self.build_search_data(), - context = new instance.web.CompoundContext(), - domain = new instance.web.CompoundDomain(); - _.each(data.contexts, function(x) { - context.add(x); - }); - _.each(data.domains, function(x) { - domain.add(x); - }); - self.rpc('/web/searchview/add_to_dashboard', { - menu_id: menu_id, - action_id: self.getParent().action.id, - context_to_save: context, - domain: domain, - view_mode: self.getParent().active_view, - name: title - }, function(r) { - if (r === false) { - self.do_warn("Could not add filter to dashboard"); - } else { - self.do_notify("Filter added to dashboard", ''); - } - }); - }} - ] - }); - }, /** * Extract search data from the view's facets. * @@ -886,13 +861,13 @@ instance.web.search.Invalid = instance.web.Class.extend( /** @lends instance.web ); } }); -instance.web.search.Widget = instance.web.OldWidget.extend( /** @lends instance.web.search.Widget# */{ +instance.web.search.Widget = instance.web.Widget.extend( /** @lends instance.web.search.Widget# */{ template: null, /** * Root class of all search widgets * * @constructs instance.web.search.Widget - * @extends instance.web.OldWidget + * @extends instance.web.Widget * * @param view the ancestor view of this widget */ @@ -1014,7 +989,7 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in this.view.query.on('add remove change reset', this.proxy('search_change')); }, start: function () { - this.$element.on('click', 'li', this.proxy('toggle_filter')); + this.$el.on('click', 'li', this.proxy('toggle_filter')); return $.when(null); }, /** @@ -1023,7 +998,7 @@ instance.web.search.FilterGroup = instance.web.search.Input.extend(/** @lends in */ search_change: function () { var self = this; - var $filters = this.$element.find('> li').removeClass('oe_selected'); + var $filters = this.$el.find('> li').removeClass('oe_selected'); var facet = this.view.query.find(_.bind(this.match_facet, this)); if (!facet) { return; } facet.values.each(function (v) { @@ -1234,7 +1209,7 @@ instance.web.search.Field = instance.web.search.Input.extend( /** @lends instanc var self = this; // A field needs a context to send when active var context = this.attrs.context; - if (!context || !facet.values.length) { + if (_.isEmpty(context) || !facet.values.length) { return; } var contexts = facet.values.map(function (facetValue) { @@ -1324,17 +1299,17 @@ instance.web.search.CharField = instance.web.search.Field.extend( /** @lends ins }); instance.web.search.NumberField = instance.web.search.Field.extend(/** @lends instance.web.search.NumberField# */{ value_from: function () { - if (!this.$element.val()) { + if (!this.$el.val()) { return null; } - var val = this.parse(this.$element.val()), - check = Number(this.$element.val()); + var val = this.parse(this.$el.val()), + check = Number(this.$el.val()); if (isNaN(val) || val !== check) { - this.$element.addClass('error'); + this.$el.addClass('error'); throw new instance.web.search.Invalid( - this.attrs.name, this.$element.val(), this.error_message); + this.attrs.name, this.$el.val(), this.error_message); } - this.$element.removeClass('error'); + this.$el.removeClass('error'); return val; } }); @@ -1515,9 +1490,16 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({ facet_for: function (value) { var self = this; if (value instanceof Array) { - return $.when(facet_from(this, value)); + if (value.length === 2 && _.isString(value[1])) { + return $.when(facet_from(this, value)); + } + assert(value.length <= 1, + _("M2O search fields do not currently handle multiple default values")); + // there are many cases of {search_default_$m2ofield: [id]}, need + // to handle this as if it were a single value. + value = value[0]; } - return this.model.call('name_get', [value], {}).pipe(function (names) { + return this.model.call('name_get', [value]).pipe(function (names) { if (_(names).isEmpty()) { return null; } return facet_from(self, names[0]); }) @@ -1530,6 +1512,15 @@ instance.web.search.ManyToOneField = instance.web.search.CharField.extend({ return [[name, '=', facetValue.get('value')]]; } return this._super(name, operator, facetValue); + }, + get_context: function (facet) { + var values = facet.values; + if (_.isEmpty(this.attrs.context) && values.length === 1) { + var c = {}; + c['default_' + this.attrs.name] = values.at(0).get('value'); + return c; + } + return this._super(facet); } }); @@ -1540,15 +1531,26 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ var self = this; this.model = new instance.web.Model('ir.filters'); this.filters = {}; - this.$element.on('submit', 'form', this.proxy('save_current')); - this.$element.on('click', 'h4', function () { - self.$element.toggleClass('oe_opened'); + this.view.query + .on('remove', function (facet) { + if (!facet.get('is_custom_filter')) { + return; + } + self.clear_selection(); + }) + .on('reset', this.proxy('clear_selection')); + this.$el.on('submit', 'form', this.proxy('save_current')); + this.$el.on('click', 'h4', function () { + self.$el.toggleClass('oe_opened'); }); // FIXME: local eval of domain and context to get rid of special endpoint return this.rpc('/web/searchview/get_filters', { model: this.view.model }).pipe(this.proxy('set_filters')); }, + clear_selection: function () { + this.$el.find('li.oe_selected').removeClass('oe_selected'); + }, append_filter: function (filter) { var self = this; var key = _.str.sprintf('(%s)%s', filter.user_id, filter.name); @@ -1559,7 +1561,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ } else { var id = filter.id; $filter = this.filters[key] = $('<li></li>') - .appendTo(this.$element.find('.oe_searchview_custom_list')) + .appendTo(this.$el.find('.oe_searchview_custom_list')) .addClass(filter.user_id ? 'oe_searchview_custom_private' : 'oe_searchview_custom_public') .text(filter.name); @@ -1571,7 +1573,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ $filter.remove(); }); }) - .prependTo($filter); + .appendTo($filter); } $filter.unbind('click').click(function () { @@ -1583,8 +1585,10 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ get_groupby: function () { return [filter.context]; }, get_domain: function () { return filter.domain; } }, + is_custom_filter: true, values: [{label: filter.name, value: null}] }]); + $filter.addClass('oe_selected'); }); }, set_filters: function (filters) { @@ -1592,8 +1596,8 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ }, save_current: function () { var self = this; - var $name = this.$element.find('input:first'); - var private_filter = !this.$element.find('input:last').prop('checked'); + var $name = this.$el.find('input:first'); + var private_filter = !this.$el.find('input:last').prop('checked'); var search = this.view.build_search_data(); this.rpc('/web/session/eval_domain_and_context', { @@ -1606,7 +1610,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ } var filter = { name: $name.val(), - user_id: private_filter ? instance.connection.uid : false, + user_id: private_filter ? instance.session.uid : false, model_id: self.view.model, context: results.context, domain: results.domain @@ -1615,7 +1619,7 @@ instance.web.search.CustomFilters = instance.web.search.Input.extend({ return self.model.call('create_or_replace', [filter]).then(function (id) { filter.id = id; self.append_filter(filter); - self.$element + self.$el .removeClass('oe_opened') .find('form')[0].reset(); }); @@ -1660,8 +1664,8 @@ instance.web.search.Filters = instance.web.search.Input.extend({ } return $.when( - this.render_column(col1, $('<div>').appendTo(this.$element)), - this.render_column(col2, $('<div>').appendTo(this.$element))); + this.render_column(col1, $('<div>').appendTo(this.$el)), + this.render_column(col2, $('<div>').appendTo(this.$el))); }, render_column: function (column, $el) { return $.when.apply(null, _(column).map(function (group) { @@ -1671,15 +1675,16 @@ instance.web.search.Filters = instance.web.search.Input.extend({ })); } }); + instance.web.search.Advanced = instance.web.search.Input.extend({ template: 'SearchView.advanced', _in_drawer: true, start: function () { var self = this; - this.$element + this.$el .on('keypress keydown keyup', function (e) { e.stopPropagation(); }) .on('click', 'h4', function () { - self.$element.toggleClass('oe_opened'); + self.$el.toggleClass('oe_opened'); }).on('click', 'button.oe_add_condition', function () { self.append_proposition(); }).on('submit', 'form', function (e) { @@ -1698,7 +1703,7 @@ instance.web.search.Advanced = instance.web.search.Input.extend({ }, append_proposition: function () { return (new instance.web.search.ExtendedSearchProposition(this, this.fields)) - .appendTo(this.$element.find('ul')); + .appendTo(this.$el.find('ul')); }, commit_search: function () { var self = this; @@ -1725,15 +1730,15 @@ instance.web.search.Advanced = instance.web.search.Input.extend({ // add new empty proposition this.append_proposition(); // TODO: API on searchview - this.view.$element.removeClass('oe_searchview_open_drawer'); + this.view.$el.removeClass('oe_searchview_open_drawer'); } }); -instance.web.search.ExtendedSearchProposition = instance.web.OldWidget.extend(/** @lends instance.web.search.ExtendedSearchProposition# */{ +instance.web.search.ExtendedSearchProposition = instance.web.Widget.extend(/** @lends instance.web.search.ExtendedSearchProposition# */{ template: 'SearchView.extended_search.proposition', /** * @constructs instance.web.search.ExtendedSearchProposition - * @extends instance.web.OldWidget + * @extends instance.web.Widget * * @param parent * @param fields @@ -1749,16 +1754,16 @@ instance.web.search.ExtendedSearchProposition = instance.web.OldWidget.extend(/* }, start: function () { var _this = this; - this.$element.find(".searchview_extended_prop_field").change(function() { + this.$el.find(".searchview_extended_prop_field").change(function() { _this.changed(); }); - this.$element.find('.searchview_extended_delete_prop').click(function () { + this.$el.find('.searchview_extended_delete_prop').click(function () { _this.destroy(); }); this.changed(); }, changed: function() { - var nval = this.$element.find(".searchview_extended_prop_field").val(); + var nval = this.$el.find(".searchview_extended_prop_field").val(); if(this.attrs.selected == null || nval != this.attrs.selected.name) { this.select_field(_.detect(this.fields, function(x) {return x.name == nval;})); } @@ -1773,7 +1778,7 @@ instance.web.search.ExtendedSearchProposition = instance.web.OldWidget.extend(/* if(this.attrs.selected != null) { this.value.destroy(); this.value = null; - this.$element.find('.searchview_extended_prop_op').html(''); + this.$el.find('.searchview_extended_prop_op').html(''); } this.attrs.selected = field; if(field == null) { @@ -1789,9 +1794,9 @@ instance.web.search.ExtendedSearchProposition = instance.web.OldWidget.extend(/* _.each(this.value.operators, function(operator) { $('<option>', {value: operator.value}) .text(String(operator.text)) - .appendTo(self.$element.find('.searchview_extended_prop_op')); + .appendTo(self.$el.find('.searchview_extended_prop_op')); }); - var $value_loc = this.$element.find('.searchview_extended_prop_value').empty(); + var $value_loc = this.$el.find('.searchview_extended_prop_value').empty(); this.value.appendTo($value_loc); }, @@ -1799,7 +1804,7 @@ instance.web.search.ExtendedSearchProposition = instance.web.OldWidget.extend(/* if ( this.attrs.selected == null) return null; var field = this.attrs.selected; - var op = this.$element.find('.searchview_extended_prop_op')[0]; + var op = this.$el.find('.searchview_extended_prop_op')[0]; var operator = op.options[op.selectedIndex]; return { label: _.str.sprintf(_t('%(field)s %(operator)s "%(value)s"'), { @@ -1843,7 +1848,7 @@ instance.web.search.ExtendedSearchProposition.Char = instance.web.search.Extende {value: "!=", text: _lt("is not equal to")} ], get_value: function() { - return this.$element.val(); + return this.$el.val(); } }); instance.web.search.ExtendedSearchProposition.DateTime = instance.web.search.ExtendedSearchProposition.Field.extend({ @@ -1867,7 +1872,7 @@ instance.web.search.ExtendedSearchProposition.DateTime = instance.web.search.Ext start: function() { var ready = this._super(); this.datewidget = new (this.widget())(this); - this.datewidget.appendTo(this.$element); + this.datewidget.appendTo(this.$el); return ready; } }); @@ -1885,11 +1890,11 @@ instance.web.search.ExtendedSearchProposition.Integer = instance.web.search.Exte {value: "<=", text: _lt("less or equal than")} ], toString: function () { - return this.$element.val(); + return this.$el.val(); }, get_value: function() { try { - return instance.web.parse_value(this.$element.val(), {'widget': 'integer'}); + return instance.web.parse_value(this.$el.val(), {'widget': 'integer'}); } catch (e) { return ""; } @@ -1909,11 +1914,11 @@ instance.web.search.ExtendedSearchProposition.Float = instance.web.search.Extend {value: "<=", text: _lt("less or equal than")} ], toString: function () { - return this.$element.val(); + return this.$el.val(); }, get_value: function() { try { - return instance.web.parse_value(this.$element.val(), {'widget': 'float'}); + return instance.web.parse_value(this.$el.val(), {'widget': 'float'}); } catch (e) { return ""; } @@ -1926,12 +1931,12 @@ instance.web.search.ExtendedSearchProposition.Selection = instance.web.search.Ex {value: "!=", text: _lt("is not")} ], toString: function () { - var select = this.$element[0]; + var select = this.$el[0]; var option = select.options[select.selectedIndex]; return option.label || option.text; }, get_value: function() { - return this.$element.val(); + return this.$el.val(); } }); instance.web.search.ExtendedSearchProposition.Boolean = instance.web.search.ExtendedSearchProposition.Field.extend({ diff --git a/addons/web/static/src/js/test_support.js b/addons/web/static/src/js/test_support.js index d46366326b7..8e1317ba2b0 100644 --- a/addons/web/static/src/js/test_support.js +++ b/addons/web/static/src/js/test_support.js @@ -1,18 +1,17 @@ openerp.test_support = { - setup_connection: function (connection) { + setup_session: function (session) { var origin = location.protocol+"//"+location.host; - _.extend(connection, { + _.extend(session, { origin: origin, prefix: origin, server: origin, // keep chs happy //openerp.web.qweb.default_dict['_s'] = this.origin; - rpc_function: connection.rpc_json, + rpc_function: session.rpc_json, session_id: false, uid: false, username: false, user_context: {}, db: false, - openerp_entreprise: false, // this.module_list = openerp._modules.slice(); // this.module_loaded = {}; // _(this.module_list).each(function (mod) { @@ -22,7 +21,7 @@ openerp.test_support = { shortcuts: [], active_id: null }); - return connection.session_reload(); + return session.session_reload(); }, module: function (title, tested_core, nonliterals) { var conf = QUnit.config.openerp = {}; @@ -31,10 +30,10 @@ openerp.test_support = { QUnit.stop(); var oe = conf.openerp = window.openerp.init(); window.openerp.web[tested_core](oe); - var done = openerp.test_support.setup_connection(oe.connection); + var done = openerp.test_support.setup_session(oe.session); if (nonliterals) { done = done.pipe(function () { - return oe.connection.rpc('/tests/add_nonliterals', { + return oe.session.rpc('/tests/add_nonliterals', { domains: nonliterals.domains || [], contexts: nonliterals.contexts || [] }).then(function (r) { diff --git a/addons/web/static/src/js/view_editor.js b/addons/web/static/src/js/view_editor.js deleted file mode 100644 index 9fdd4f0bca1..00000000000 --- a/addons/web/static/src/js/view_editor.js +++ /dev/null @@ -1,1207 +0,0 @@ -openerp.web.view_editor = function(instance) { -var _t = instance.web._t; -var QWeb = instance.web.qweb; -instance.web.ViewEditor = instance.web.OldWidget.extend({ - init: function(parent, element_id, dataset, view, options) { - this._super(parent); - this.element_id = element_id; - this.parent = parent; - this.dataset = new instance.web.DataSetSearch(this, 'ir.ui.view', null, null), - this.model = dataset.model; - this.xml_element_id = 0; - this.property = instance.web.ViewEditor.property_widget; - this.one_object = false; - }, - start: function() { - this.init_view_editor(); - }, - init_view_editor: function() { - var self = this, - action_title = _.str.sprintf(_t("Manage Views (%s)"), this.model); - var action = { - name: action_title, - context: this.session.user_context, - domain: [["model", "=", this.model]], - res_model: 'ir.ui.view', - views: [[false, 'list']], - type: 'ir.actions.act_window', - target: "current", - limit: this.dataset.limit || 80, - auto_search : true, - flags: { - sidebar: false, - deletable: false, - views_switcher: false, - action_buttons: false, - search_view: false, - pager: false, - radio: true, - select_view_id: self.parent.fields_view.view_id - } - }; - this.view_edit_dialog = new instance.web.Dialog(this, { - title: action_title, - width: 850, - buttons: [ - {text: _t("Create"), click: function() { self.on_create_view(); }}, - {text: _t("Edit"), click: function() { self.xml_element_id = 0; self.get_arch(); }}, - {text: _t("Remove"), click: function() { self.do_delete_view(); }}, - {text: _t("Close"), click: function() { self.view_edit_dialog.close(); window.location.reload(); }} - ] - }).open(); - this.view_edit_dialog.on_close.add_last(function(){window.location.reload();}); - this.main_view_id = this.parent.fields_view.view_id; - this.action_manager = new instance.web.ActionManager(this); - $.when(this.action_manager.do_action(action)).then(function() { - var viewmanager = self.action_manager.inner_viewmanager, - controller = viewmanager.views[viewmanager.active_view].controller; - self.action_manager.appendTo(self.view_edit_dialog.$element); - self.action_manager.renderElement(self.view_edit_dialog); - controller.on_loaded.add_last(function(){ - $(controller.groups).bind({ - 'selected': function(e, ids, records) { - self.main_view_id = ids[0]; - } - }) - }); - }); - }, - on_create_view: function() { - var self = this; - this.create_view_dialog = new instance.web.Dialog(this, { - title: _.str.sprintf(_t("Create a view (%s)"), self.model), - buttons: [ - {text: _t("Save"), click: function () { - var view_values = {}; - var warn = false; - _.each(self.create_view_widget, function(widget) { - if (widget.is_invalid) { - warn = true; - return false; - } - if (widget.dirty && !widget.is_invalid) { - view_values[widget.name] = widget.get_value(); - } - }); - if (warn) { - self.on_valid_create_view(self.create_view_widget); - } else { - $.when(self.do_save_view(view_values)).then(function() { - self.create_view_dialog.close(); - var controller = self.action_manager.inner_viewmanager.views[self.action_manager.inner_viewmanager.active_view].controller; - controller.reload_content(); - }); - } - }}, - {text: _t("Cancel"), click: function () { self.create_view_dialog.close(); }} - ] - }).open(); - var view_widget = [{'name': 'view_name', 'string':'View Name', 'type': 'char', 'required': true, 'value' : this.model + '.custom_' + Math.round(Math.random() * 1000)}, - {'name': 'view_type', 'string': 'View Type', 'type': 'selection', 'required': true, 'value': 'Form', 'selection': [['',''],['tree', 'Tree'],['form', 'Form'],['graph', 'Graph'],['calendar', 'Calender']]}, - {'name': 'proirity', 'string': 'Priority', 'type': 'float', 'required': true, 'value':'16'}]; - this.create_view_dialog.$element.append('<table id="create_view" style="width:400px" class="oe_form"></table>'); - this.create_view_widget = []; - _.each(view_widget, function(widget) { - var type_widget = new (self.property.get_any([widget.type])) (self.create_view_dialog, widget); - self.create_view_dialog.$element.find('table[id=create_view]').append('<tr><td width="100px" align="right">' + widget.string + ':</td>' + type_widget.render()+'</tr>'); - var value = null; - if (widget.value) { - value = widget.value; - type_widget.dirty = true; - } - type_widget.start(); - type_widget.set_value(value); - self.create_view_widget.push(type_widget); - }); - }, - do_save_view: function(values) { - def = $.Deferred(); - var field_dataset = new instance.web.DataSetSearch(this, this.model, null, null); - var model_dataset = new instance.web.DataSetSearch(this, 'ir.model', null, null); - var view_string = "", field_name = false, self = this; - field_dataset.call( 'fields_get', [], function(fields) { - _.each(['name', 'x_name'], function(value) { - if (_.include(_.keys(fields), value)) { - field_name = value; - return false; - } - }); - if (field_name) { - model_dataset.read_slice(['name','field_id'], {"domain": [['model','=',self.model]]}).then(function(records) { - if (records) {view_string = records[0].name;} - var arch = _.str.sprintf("<?xml version='1.0'?>\n<%s string='%s'>\n\t<field name='%s'/>\n</%s>", values.view_type, view_string, field_name, values.view_type); - var vals = {'model': self.model, 'name': values.view_name, 'priority': values.priority, 'type': values.view_type, 'arch': arch}; - self.dataset.create(vals, function(suc) { - def.resolve(); - }); - }); - } - }); - return def.promise(); - }, - on_valid_create_view: function(widgets) { - var msg = "<ul>"; - _.each(widgets, function(widget) { - if (widget.is_invalid) { - msg += "<li>" + widget.name + "</li>"; - } - }); - msg += "</ul>"; - this.do_warn("The following fields are invalid :", msg); - }, - add_node_name : function(node) { - if(node.tagName.toLowerCase() == "button" || node.tagName.toLowerCase() == "field"){ - return (node.getAttribute('name'))? - _.str.sprintf( "<%s name='%s'>",node.tagName.toLowerCase(), node.getAttribute('name')): - _.str.sprintf( "<%s>",node.tagName.toLowerCase()); - }else if(node.tagName.toLowerCase() == "group"){ - return (node.getAttribute('string'))? - _.str.sprintf( "<%s>",node.getAttribute('string')): - _.str.sprintf( "<%s>",node.tagName.toLowerCase()); - }else{ - return (node.getAttribute('string'))? - _.str.sprintf( "<%s string='%s'>",node.tagName.toLowerCase(), node.getAttribute('string')): - _.str.sprintf( "<%s>",node.tagName.toLowerCase()); - } - }, - do_delete_view: function() { - var self = this; - if (confirm(_t("Do you really want to remove this view?"))) { - var controller = this.action_manager.inner_viewmanager.views[this.action_manager.inner_viewmanager.active_view].controller; - this.dataset.unlink([this.main_view_id]).then(function() { - controller.reload_content(); - self.main_view_id = self.parent.fields_view.view_id; - }); - } - }, - create_View_Node: function(node){ - ViewNode = { - 'level': ($(node).parents()).length + 1, - 'id': this.xml_element_id += 1, - 'att_list': [], - 'name': this.add_node_name(node), - 'child_id': [] - }; - ViewNode.att_list.push(node.tagName.toLowerCase()); - _.each(node.attributes, function(att) { - ViewNode.att_list.push([att.nodeName, att.nodeValue]); - }); - return ViewNode; - }, - append_child_object: function(main_object, parent_id, child_obj_list) { - var self = this; - if (main_object.id == parent_id) { - main_object.child_id = child_obj_list; - return main_object; - } else { - _.each(main_object.child_id, function(child_object) { - self.append_child_object(child_object, parent_id, child_obj_list); - }); - } - }, - convert_arch_to_obj: function(xml_Node, main_object, parent_id) { - var self = this; - var child_obj_list = []; - _.each(xml_Node, function(element) { - child_obj_list.push(self.create_View_Node(element)); - }); - this.append_child_object(main_object, parent_id, child_obj_list); - var obj_xml_list = _.zip(xml_Node, child_obj_list); - _.each(obj_xml_list, function(node) { - var children = _.filter(node[0].childNodes, function(child) { - return child.nodeType == 1; - }); - if (children) { - self.convert_arch_to_obj(children, main_object, node[1].id); - } - }); - return main_object; - }, - parse_xml: function(arch, view_id) { - main_object = { - 'level': 0, - 'id': this.xml_element_id +=1, - 'att_list': [], - 'name': _.str.sprintf("<view view_id = %s>", view_id), - 'child_id': [] - }; - var xml_arch = QWeb.load_xml(arch); - return [this.convert_arch_to_obj(xml_arch.childNodes, main_object, this.xml_element_id)]; - }, - get_arch: function() { - var self = this; - var view_arch_list = []; - this.dataset.read_ids([parseInt(self.main_view_id)], ['arch', 'type','priority']).then(function(arch) { - if (arch.length) { - var arch_object = self.parse_xml(arch[0].arch, self.main_view_id); - self.main_view_type = arch[0].type == 'tree'? 'list': arch[0].type; - view_arch_list.push({"view_id": self.main_view_id, "arch": arch[0].arch,"priority":arch[0].priority}); - self.dataset.read_slice([], {domain: [['inherit_id','=', parseInt(self.main_view_id)]]}).then(function(result) { - _.each(result, function(res) { - view_arch_list.push({"view_id": res.id, "arch": res.arch,"priority":res.priority}); - self.inherit_view(arch_object, res); - }); - return self.edit_view({"main_object": arch_object, - "parent_child_id": self.parent_child_list(arch_object, []), - "arch": view_arch_list}); - }); - } else { - self.do_warn("Please select view in list :"); - } - }); - }, - parent_child_list : function(one_object, parent_list) { - var self = this; - _.each(one_object , function(element) { - if (element.child_id.length != 0) { - parent_list.push({"key": element.id, "value": _.pluck(element.child_id, 'id')}); - self.parent_child_list(element.child_id, parent_list); - } - }); - return parent_list; - }, - inherit_view : function(arch_object, result) { - var self = this, xml_list = [], xml_arch = QWeb.load_xml(result.arch); - if (xml_arch.childNodes[0].tagName == "data") { - xml_list = _.filter(xml_arch.childNodes[0].childNodes, function(child) { - return child.nodeType == 1; - }); - } else { - xml_list.push( xml_arch.childNodes[0]); - } - _.each(xml_list, function(xml) { - var expr_to_list = [], xpath_arch_object = self.parse_xml(QWeb.tools.xml_node_to_string(xml), result.id); - if (xml.tagName == "xpath") { - var part_expr = _.without(xml.getAttribute('expr').split("/"), ""); - _.each(part_expr, function(part) { - expr_to_list.push(_.without($.trim(part.replace(/[^a-zA-Z 0-9 _]+/g,'!')).split("!"), "")); - }); - } else { - var temp = _.reject(xpath_arch_object[0].child_id[0].att_list, function(list) { - return list instanceof Array? _.include(list, "position"): false; - }); - expr_to_list = [_.flatten(temp)]; - } - self.inherit_apply(expr_to_list, arch_object ,xpath_arch_object); - }); - }, - inherit_apply: function(expr_list ,arch_object ,xpath_arch_object) { - var self = this; - if (xpath_arch_object.length) { - var check = expr_list[0], obj = false; - switch (check.length) { - case 2: - if (parseInt(check[1])) { - //for field[3] - var temp_list = _.select(arch_object, function(element) { - return _.include(_.flatten(element.att_list), check[0]); - }); - obj = arch_object[_.indexOf(arch_object, temp_list[parseInt(check[1]) - 1])]; - } else { - //for notebook[last()] - obj = _.detect(arch_object, function(element) { - return _.include(_.flatten(element.att_list), check[0]); - }); - } - break; - case 3: - //for field[@name='type'] - obj = _.detect(arch_object, function(element){ - if ((_.intersection(_.flatten(element.att_list), _.uniq(check))).length == _.uniq(check).length) { - return element; - } - }); - break; - case 1: - //for /form/notebook - var temp_list = _.select(arch_object, function(element) { - return _.include(_.flatten(element.att_list), check[0]); - }); - if (temp_list.length != 0) { - expr_list.length == 1 ? obj = temp_list[0] : expr_list.shift(); - } - break; - } - if (obj) { - expr_list.shift(); - if (expr_list.length) { - self.inherit_apply(expr_list, obj.child_id, xpath_arch_object); - } else { - self.increase_level(xpath_arch_object[0], obj.level + 1); - obj.child_id.push(xpath_arch_object[0]); - xpath_arch_object.pop(); - } - } else { - _.each(arch_object, function(element) { - self.inherit_apply(expr_list, element.child_id, xpath_arch_object); - }); - } - } - }, - increase_level: function(val, level) { - var self = this; - val.level = level; - _.each(val.child_id, function(val, key) { - self.increase_level(val, level + 1); - }); - }, - do_select_row: function(row_id) { - this.edit_xml_dialog.$element.find("tr[id^='viewedit-']").removeClass('ui-selected'); - this.edit_xml_dialog.$element.find("tr[id=viewedit-" + row_id + "]").addClass('ui-selected'); - }, - do_parent_img_hide_show: function(img) { - if ($(img).attr('src') == '/web/static/src/img/collapse.gif') { - $(img).attr('src', '/web/static/src/img/expand.gif'); - this.on_expand(img); - } else { - $(img).attr('src', '/web/static/src/img/collapse.gif'); - this.on_collapse(img); - } - }, - edit_view: function(one_object) { - var self = this; - this.one_object = one_object; - this.edit_xml_dialog = new instance.web.Dialog(this, { - title: _.str.sprintf(_t("View Editor %d - %s"), self.main_view_id, self.model), - height: '90%', - buttons: [ - {text: _t("Inherited View"), click: function(){ - var selected_row = self.edit_xml_dialog.$element.find('.ui-selected'); - if (selected_row.length) { - if(selected_row.find('a').text().search("field") != -1){ - if (confirm(_t("Do you really wants to create an inherited view here?"))) { - self.inherited_view(selected_row); - } - }else{ - alert("Can't Update View"); - } - }else{ - alert("Select an element"); - } - }}, - {text: _t("Preview"), click: function() { - var action = { - context: self.session.user_context, - res_model: self.model, - views: [[self.main_view_id, self.main_view_type]], - type: 'ir.actions.act_window', - target: "new", - auto_search: true, - flags: { - sidebar: false, - views_switcher: false, - action_buttons: false - } - }; - var action_manager = new instance.web.ActionManager(self); - action_manager.do_action(action); - }}, - {text: _t("Close"), click: function(){ - self.action_manager.inner_viewmanager.views[self.action_manager.inner_viewmanager.active_view].controller.reload_content(); - self.edit_xml_dialog.close(); - }} - ] - }).open(); - var no_property_att = []; - _.each(_PROPERTIES, function(val, key) { - if (! val.length) no_property_att.push(key); - }); - this.edit_xml_dialog.$element.html(QWeb.render('view_editor', {'data': one_object['main_object'], 'no_properties': no_property_att})); - this.edit_xml_dialog.$element.find("tr[id^='viewedit-']").click(function() { - self.do_select_row(this.id.split('-')[1]); - }); - this.edit_xml_dialog.$element.find("img[id^='parentimg-']").click(function() { - self.do_parent_img_hide_show(this); - }); - this.edit_xml_dialog.$element.find("img[id^='side-']").click(function() { - self.on_select_img(this); - }); - }, - inherited_view: function(selected_row){ - var self = this; - var row_id = parseInt((selected_row.attr('id')).split('-')[1]); - var obj = self.get_object_by_id(row_id,self.one_object['main_object'], [])[0]; - var view_name = this.model + '.inherit_' + Math.round(Math.random() * 1000); - var view_find = selected_row; - var view_id; - var min_level = parseInt(selected_row.attr('level')); - while (1) { - view_find = view_find.prev(); - if (view_find.length == 0 || - self.edit_xml_dialog.$element.find(view_find).find('a').text().search("view_id") != -1 && - parseInt(view_find.attr('level')) < min_level ) { - view_id = parseInt($(view_find).find('a').text().replace(/[^0-9]+/g, '')); - break; - } - if (view_find.attr('level') < min_level) { - min_level = parseInt(view_find.attr('level')); - } - } - var val = _.detect(obj.att_list, function(val) {return val[0] == "name";}); - var priority = _.detect(self.one_object['arch'], function(val) {return val.view_id == view_id;}); - var arch = _.str.sprintf("<?xml version='1.0'?>\n\t <field name='%s' position='after'> </field>", val[1]); - var vals = {'model': self.model, 'name': view_name, 'priority': priority.priority + 1, 'type': "form", 'arch': arch,'inherit_id':self.main_view_id}; - this.dataset.create(vals, function(suc) { - var arch_to_obj = self.parse_xml(arch,suc.result); - obj.child_id.push(arch_to_obj[0]); - self.one_object['parent_child_id'] = self.parent_child_list(self.one_object['main_object'],[]); - self.one_object['arch'].push({'view_id':suc.result,"arch":arch,'priority': priority.priority + 1}); - self.increase_level(arch_to_obj[0],obj.level+1); - self.render_inherited_view(selected_row,arch_to_obj[0]); - }); - }, - render_inherited_view: function(selected_row,obj){ - var self = this,row_id = parseInt((selected_row.attr('id')).split('-')[1]); - var clone = this.create_clone(selected_row.clone(),obj); - if (selected_row.find("img[id^='parentimg-']").length == 0) { - ($(selected_row.find('a').parent()).siblings('td')) - .append($('<img width="16" height="16"></img>').attr('src', '/web/static/src/img/collapse.gif'). - attr('id','parentimg-'+ row_id).click(function(){ - self.do_parent_img_hide_show(this); - })); - } - self.edit_xml_dialog.$element. - find("tr[id='viewedit-"+row_id+"']").after(clone.removeClass('ui-selected')); - _.each(obj.child_id,function(obj){self.render_inherited_view(clone,obj);}); - }, - on_select_img: function(element_img) { - var self = this; - var side = $(element_img).closest("tr[id^='viewedit-']"); - this.one_object.clicked_tr_id = parseInt((side.attr('id')).split('-')[1]); - this.one_object.clicked_tr_level = parseInt(side.attr('level')); - var img = side.find("img[id='parentimg-" + this.one_object.clicked_tr_id + "']").attr('src'); - var view_id = 0, view_xml_id = 0, view_find = side; - //for view id finding - var min_level = this.one_object.clicked_tr_id; - if (($(side).find('a').text()).search("view_id") != -1) { - view_id = parseInt(($(view_find).find('a').text()).replace(/[^0-9]+/g, '')); - view_xml_id = (view_find.attr('id')).split('-')[1]; - this.one_object.clicked_tr_id += 1; - this.one_object.clicked_tr_level += 1; - }else{ - while (1) { - view_find = view_find.prev(); - if (view_find.length == 0 || - (self.edit_xml_dialog.$element.find(view_find).find('a').text()).search("view_id") != -1 - && parseInt(view_find.attr('level')) < min_level ) { - view_id = parseInt(($(view_find).find('a').text()).replace(/[^0-9]+/g, '')); - view_xml_id = parseInt((view_find.attr('id')).split('-')[1]); - break; - } - if (view_find.attr('level') < min_level) { - min_level = parseInt(view_find.attr('level')); - } - } - } - this.one_object.clicked_tr_view = [view_id, view_xml_id]; - switch (element_img.id) { - case "side-add": - self.do_node_add(side); - break; - case "side-remove": - if (confirm(_t("Do you really want to remove this node?"))) { - self.do_save_update_arch("remove_node"); - } - break; - case "side-edit": - self.do_node_edit(side); - break; - case "side-up": - self.do_node_up(side, img); - break; - case "side-down": - self.do_node_down(side, img); - break; - } - }, - do_node_add: function(side){ - var self = this; - var tr = self.get_object_by_id(this.one_object.clicked_tr_id, this.one_object['main_object'], [])[0].att_list[0]; - var parent_tr = ($(side).prevAll("tr[level=" + String(this.one_object.clicked_tr_level - 1) + "]"))[0]; - var field_dataset = new instance.web.DataSetSearch(this, this.model, null, null); - parent_tr = $(parent_tr).find('a').text(); - field_dataset.call( 'fields_get', [], function(result) { - var fields = _.keys(result); - fields.push(" "),fields.sort(); - var property_to_check = []; - _.each([tr, parent_tr],function(element) { - property_to_check.push( - _.detect(_.keys(_CHILDREN),function(res){ - return _.str.include(element, res); - })); - }); - self.on_add_node(property_to_check, fields); - }); - }, - do_node_edit: function(side) { - var self = this; - var result = self.get_object_by_id(this.one_object.clicked_tr_id, this.one_object['main_object'], []); - if (result.length && result[0] && result[0].att_list) { - var properties = _PROPERTIES[result[0].att_list[0]]; - self.on_edit_node(properties); - } - }, - do_node_down: function(cur_tr, img) { - var self = this, next_tr, last_tr, tr_to_move = []; - tr_to_move.push(cur_tr); - if (img) { - while (1) { - next_tr = cur_tr.next(); - if ( parseInt(next_tr.attr('level')) <= this.one_object.clicked_tr_level || next_tr.length == 0) { - last_tr = next_tr; - break; - } else { - tr_to_move.push(next_tr); - cur_tr = next_tr; - } - } - } else { - last_tr = cur_tr.next(); - } - if ((self.edit_xml_dialog.$element.find(last_tr).find('a').text()).search("view_id") != -1) { - return false; - } - if (last_tr.length != 0 && parseInt(last_tr.attr('level')) == this.one_object.clicked_tr_level) { - var last_tr_id = (last_tr.attr('id')).split('-')[1]; - img = last_tr.find("img[id='parentimg-" + last_tr_id + "']").attr('src'); - if (img) { - self.edit_xml_dialog.$element.find("img[id='parentimg-" + last_tr_id + "']"). - attr('src', '/web/static/src/img/expand.gif'); - while (1) { - var next_tr = last_tr.next(); - if (next_tr.attr('level') <= this.one_object.clicked_tr_level || next_tr.length == 0) break; - next_tr.hide(); - last_tr = next_tr; - } - } - tr_to_move.reverse(); - _.each(tr_to_move, function(rec) { - $(last_tr).after(rec); - }); - self.do_save_update_arch("down"); - } - }, - do_node_up: function(cur_tr, img) { - var self = this, side = cur_tr, tr_to_move = []; - tr_to_move.push(side); - while (1) { - var prev_tr = cur_tr.prev(); - if (this.one_object.clicked_tr_level >= parseInt(prev_tr.attr('level')) || prev_tr.length == 0) { - last_tr = prev_tr; - break; - } - cur_tr = prev_tr; - } - if (img) { - self.edit_xml_dialog.$element.find("img[id='parentimg-" + this.one_object.clicked_tr_id + "']"). - attr('src', '/web/static/src/img/expand.gif'); - while (1) { - next_tr = side.next(); - if (parseInt(next_tr.attr('level')) <= this.one_object.clicked_tr_level || next_tr.length == 0) { - break; - } else { - next_tr.hide(); - tr_to_move.push(next_tr); - side = next_tr; - } - } - } - if (last_tr.length != 0 && parseInt(last_tr.attr('level')) == this.one_object.clicked_tr_level && - (self.edit_xml_dialog.$element.find(last_tr).find('a').text()).search("view_id") == -1) { - _.each(tr_to_move, function(rec) { - $(last_tr).before(rec); - }); - self.do_save_update_arch("up"); - } - }, - do_save_update_arch: function(move_direct, update_values) { - var self = this; - var arch = _.detect(self.one_object['arch'], function(element) - {return element.view_id == self.one_object.clicked_tr_view[0]}); - var obj = self.get_object_by_id(this.one_object.clicked_tr_view[1],this.one_object['main_object'], []); - //for finding xpath tag from inherit view - var xml_arch = QWeb.load_xml(arch.arch); - if (xml_arch.childNodes[0].tagName == "data") { - var check_list = _.flatten(obj[0].child_id[0].att_list); - var children = _.filter(xml_arch.childNodes[0].childNodes, function (child) { - return child.nodeType == 1; - }); - arch.arch = _.detect(children, function(xml_child) { - var temp_obj = self.create_View_Node(xml_child), - insert = _.intersection(_.flatten(temp_obj.att_list),_.uniq(check_list)); - if (insert.length == _.uniq(check_list).length ) {return xml_child;} - }); - xml_arch = QWeb.load_xml(arch.arch); - } - return self.do_save_xml(xml_arch.documentElement, obj[0].child_id[0],obj[0].child_id, move_direct, update_values,arch); - }, - get_object_by_id: function(id, one_object, result) { - var self = this; - if (result.length == 0 ) { - var check = _.detect(one_object , function(obj) { - return id == obj.id; - }); - if (check) {result.push(check);} - _.each(one_object, function(obj) { - self.get_object_by_id(id,obj.child_id, result); - }); - } - return result; - }, - create_clone: function(clone, new_node_obj){ - var self = this; - clone.find('a').text(new_node_obj.name); - ($(clone.find('a').parent()).siblings('td')).css( "padding-left", 20 * new_node_obj.level); - clone.attr("id", "viewedit-" + new_node_obj.id); - clone.attr("level", new_node_obj.level); - clone.find("img[id^='parentimg-']").remove(); - clone.bind("click",function(){ - self.do_select_row(this.id.split('-')[1]); - }); - clone.find("img[id^='side-']").click(function() { - self.on_select_img(this); - }); - return clone; - }, - do_save_xml: function(arch1, obj, child_list, move_direct, update_values, arch){ - var self = this, children_list = $(arch1).children(),list_obj_xml; - try{list_obj_xml = _.zip(children_list, obj.child_id);}catch(err){return;} - if (this.one_object.clicked_tr_id) { - if (obj.id == this.one_object.clicked_tr_id) { - var parent = false, index = _.indexOf(child_list, obj); - if (move_direct == "down") { - var next = $(arch1).next(); - $(next).after(arch1); - var re_insert_obj = child_list.splice(index, 1); - child_list.splice(index+1, 0, re_insert_obj[0]); - parent = $(arch1).parents(); - } else if (move_direct == "up") { - var prev = $(arch1).prev(); - $(prev).before(arch1); - var re_insert_obj = child_list.splice(index, 1); - child_list.splice(index-1, 0, re_insert_obj[0]); - parent = $(arch1).parents(); - } else if (move_direct == "update_node") { - _.each(update_values, function(val){ - if (val[1]) $(arch1)[0].setAttribute(val[0], val[1]); - else $(arch1)[0].removeAttribute(val[0]); - }); - var new_obj = self.create_View_Node(arch1); - new_obj.id = obj.id,new_obj.child_id = obj.child_id; - self.edit_xml_dialog.$element. - find("tr[id='viewedit-"+this.one_object.clicked_tr_id+"']"). - find('a').text(new_obj.name); - child_list.splice(index, 1, new_obj); - parent = $(arch1).parents(); - } else if(move_direct == "add_node") { - var tr_click = self.edit_xml_dialog.$element.find("tr[id='viewedit-"+self.one_object.clicked_tr_id+"']"), - temp_xml = QWeb.load_xml(update_values[0]), - object_xml = self.create_View_Node(temp_xml.childNodes[0]); - (update_values[1] == "Inside")? object_xml.level = obj.level + 1:object_xml.level = obj.level; - var clone = self.create_clone(tr_click.clone(),object_xml), - after_append = _.detect(self.one_object['parent_child_id'],function(ele){ - return self.one_object.clicked_tr_id == ele.key; - }); - after_append = (after_append)?_.last(after_append.value):self.one_object.clicked_tr_id; - switch (update_values[1]) { - case "After": - self.edit_xml_dialog.$element. - find("tr[id='viewedit-"+after_append+"']").after(clone); - $(arch1).after($(update_values[0])); - child_list.splice(index + 1, 0, object_xml); - break; - case "Before": - tr_click.before(clone); - $(arch1).before($(update_values[0])); - child_list.splice(index - 1, 0, object_xml); - break; - case "Inside": - if (tr_click.find("img[id^='parentimg-']").length == 0) { - ($(tr_click.find('a').parent()).siblings('td')) - .append($('<img width="16" height="16"></img>').attr('src', '/web/static/src/img/collapse.gif'). - attr('id','parentimg-'+ self.one_object.clicked_tr_id).click(function(){ - self.do_parent_img_hide_show(this); - })); - } - $(arch1).append($(update_values[0])); - self.edit_xml_dialog.$element. - find("tr[id='viewedit-"+after_append+"']").after(clone); - obj.child_id.push(object_xml); - break; - } - self.edit_xml_dialog.$element. - find("tr[id='viewedit-" + object_xml.id + "']").removeClass('ui-selected'); - parent = $(arch1).parents(); - } else if (move_direct == "remove_node") { - parent = $(arch1).parents(); - if (parent.length == 0 || (parent[0].tagName.toLowerCase() == "data")) { - self.one_object.clicked_tr_id = self.one_object.clicked_tr_id -1; - self.one_object.clicked_tr_level = self.one_object.clicked_tr_level - 1; - (parent.length == 0)?parent.push("remove_view"):false; - } - $(arch1).remove(); - child_list.splice(index,1); - var cur_tr = self.edit_xml_dialog.$element. - find("tr[id='viewedit-" + self.one_object.clicked_tr_id + "']"); - _.each(self.get_list_tr(cur_tr,self.one_object.clicked_tr_level), function(tr_element){ - tr_element.remove(); - }); - cur_tr.remove(); - var parent_img = _.detect(self.one_object['parent_child_id'],function(element){ - return _.include(element.value, self.one_object.clicked_tr_id); - }); - if(parent_img.value.length == 1){ - self.edit_xml_dialog.$element. - find("tr[id='viewedit-"+parent_img.key+"']"). - find("img[id^='parentimg-']").remove(); - } - self.one_object['parent_child_id'] = self.parent_child_list(self.one_object['main_object'],[]); - } - var convert_to_utf = (parent.length != 0)? parent[parent.length-1]: arch1; - if (convert_to_utf != "remove_view") { - convert_to_utf = QWeb.tools.xml_node_to_string(convert_to_utf); - convert_to_utf = convert_to_utf.replace('xmlns="http://www.w3.org/1999/xhtml"', ""); - convert_to_utf = '<?xml version="1.0"?>' + convert_to_utf; - arch.arch = convert_to_utf; - this.dataset.write(this.one_object.clicked_tr_view[0] ,{"arch":convert_to_utf}, function(r) {}); - } else { - this.dataset.unlink([this.one_object.clicked_tr_view[0]],function(res) {}); - } - if(move_direct == "add_node"){ - self.add_node_dialog.close(); - self.on_select_img(clone.find("img[id='side-edit']")[0]); - self.one_object['parent_child_id'] = self.parent_child_list(self.one_object['main_object'],[]); - } - } - if (obj.level <= this.one_object.clicked_tr_level) { - _.each(list_obj_xml, function(child_node) { - self.do_save_xml(child_node[0], child_node[1], obj.child_id, move_direct, update_values, arch); - }); - } - } - }, - on_expand: function(expand_img){ - var level = parseInt($(expand_img).closest("tr[id^='viewedit-']").attr('level')); - var cur_tr = $(expand_img).closest("tr[id^='viewedit-']"); - _.each(this.get_list_tr(cur_tr,level), function(tr_element){ - tr_element.hide(); - }); - }, - get_list_tr: function(cur_tr,level){ - tr_list = []; - while (1) { - var nxt_tr = cur_tr.next(); - if (parseInt(nxt_tr.attr('level')) > level) { - cur_tr = nxt_tr; - tr_list.push(nxt_tr); - } else return tr_list; - } - }, - on_collapse: function(collapse_img) { - var self = this, id = collapse_img.id.split('-')[1]; - var datas = _.detect(self.one_object['parent_child_id'] , function(res) { - return res.key == id; - }); - _.each(datas.value, function (rec) { - var tr = self.edit_xml_dialog.$element.find("tr[id='viewedit-" + rec + "']"); - tr.find("img[id='parentimg-" + rec + "']").attr('src', '/web/static/src/img/expand.gif'); - tr.show(); - }); - }, - on_edit_node: function(properties){ - var self = this; - this.edit_node_dialog = new instance.web.Dialog(this,{ - title: _t("Properties"), - width: 450, - buttons: [ - {text: _t("Update"), click: function () { - var warn = false, update_values = []; - _.each(self.edit_widget, function(widget) { - if (widget.is_invalid) { - warn = true; - return false; - } - if (widget.dirty && !widget.is_invalid) { - update_values.push([widget.name, widget.get_value()]); - } - }); - if (warn) { - self.on_valid_create_view(self.edit_widget); - } else { - self.do_save_update_arch("update_node", update_values); - self.edit_node_dialog.close(); - } - }}, - {text: _t("Cancel"), click: function () { self.edit_node_dialog.close(); }} - ] - }).open(); - var _PROPERTIES_ATTRIBUTES = { - 'name' : {'name':'name', 'string': 'Name', 'type': 'char'}, - 'string' : {'name':'string', 'string': 'String', 'type': 'char'}, - 'required' : {'name':'required', 'string': 'Required', 'type': 'boolean'}, - 'readonly' : {'name':'readonly', 'string': 'Readonly', 'type': 'boolean'}, - 'invisible' : {'name':'invisible', 'string': 'Invisible', 'type': 'boolean'}, - 'domain' : {'name':'domain', 'string': 'Domain', 'type': 'char'}, - 'context' : {'name':'context', 'string': 'Context', 'type': 'char'}, - 'limit' : {'name':'limit', 'string': 'Limit', 'type': 'float'}, - 'min_rows' : {'name':'min_rows', 'string': 'Minimum rows', 'type': 'float'}, - 'date_start' : {'name':'date_start', 'string': 'Start date', 'type': 'char'}, - 'date_delay' : {'name':'date_delay', 'string': 'Delay date', 'type': 'char'}, - 'day_length' : {'name':'day_length', 'string': 'Day length', 'type': 'char'}, - 'mode' : {'name':'mode', 'string': 'Mode', 'type': 'char'}, - 'align' : {'name':'align', 'string': 'Alignment ', 'type': 'selection', 'selection': [['', ''], ['0.0', 'Left'], ['0.5', 'Center'], ['1.0', 'Right']]}, - 'icon' : {'name':'icon', 'string': 'Icon', 'type': 'selection', 'selection': _ICONS}, - 'type' : {'name':'type', 'string': 'Type', 'type': 'selection', 'selection': [['', ''], ['action', 'Action'], ['object', 'Object'], ['workflow', 'Workflow'], ['server_action', 'Server Action']]}, - 'special' : {'name':'special', 'string': 'Special', 'type': 'selection', 'selection': [['',''],['save', 'Save Button'], ['cancel', 'Cancel Button'], ['open', 'Open Button']]}, - 'target' : {'name':'target', 'string': 'Target', 'type': 'selection', 'selection': [['', ''], ['new', 'New Window']]}, - 'confirm' : {'name':'confirm', 'string': 'Confirm', 'type': 'char'}, - 'style' : {'name':'style', 'string': 'Style', 'type': 'selection', 'selection':[["",""],["1", "1"],["1-1", "1-1"],["1-2", "1-2"],["2-1", "2-1"],["1-1-1", "1-1-1"]]}, - 'filename' : {'name':'filename', 'string': 'File Name', 'type': 'char'}, - 'width' : {'name':'width', 'string': 'Width', 'type': 'float'}, - 'height' : {'name':'height', 'string': 'Height', 'type': 'float'}, - 'attrs' : {'name':'attrs', 'string': 'Attrs', 'type': 'char'}, - 'col' : {'name':'col', 'string': 'col', 'type': 'float'}, - 'link' : {'name':'link', 'string': 'Link', 'type': 'char'}, - 'position' : {'name':'position', 'string': 'Position', 'type': 'selection', 'selection': [['',''],['after', 'After'],['before', 'Before'],['inside', 'Inside'],['replace', 'Replace']]}, - 'states' : {'name':'states', 'string': 'states', 'type': 'char'}, - 'eval' : {'name':'eval', 'string': 'Eval', 'type': 'char'}, - 'ref' : {'name':'ref', 'string': 'Ref', 'type': 'char'}, - 'on_change' : {'name':'on_change', 'string': 'On change', 'type': 'char'}, - 'nolabel' : {'name':'nolabel', 'string': 'No label', 'type': 'boolean'}, - 'completion' : {'name':'completion', 'string': 'Completion', 'type': 'boolean'}, - 'colspan' : {'name':'colspan', 'string': 'Colspan', 'type': 'float'}, - 'widget' : {'name':'widget', 'string': 'widget', 'type': 'selection'}, - 'colors' : {'name':'colors', 'string': 'Colors', 'type': 'char'}, - 'editable' : {'name':'editable', 'string': 'Editable', 'type': 'selection', 'selection': [["",""],["top","Top"],["bottom", "Bottom"]]}, - 'groups' : {'name':'groups', 'string': 'Groups', 'type': 'selection_multi'}, - 'fonts' : {'name':'fonts', 'string': 'fonts', 'type': 'char'}, - }; - var arch_val = self.get_object_by_id(this.one_object.clicked_tr_id,this.one_object['main_object'], []); - this.edit_node_dialog.$element.append('<table id="rec_table" style="width:400px" class="oe_form"></table>'); - this.edit_widget = []; - self.ready = $.when(self.on_groups(properties)).then(function () { - _PROPERTIES_ATTRIBUTES['groups']['selection'] = self.groups; - var values = _.keys( instance.web.form.widgets.map); - values.push(''); - values.sort(); - _PROPERTIES_ATTRIBUTES['widget']['selection'] = values; - var widgets = _.filter(_PROPERTIES_ATTRIBUTES, function (property) { return _.include(properties, property.name)}); - _.each(widgets, function(widget) { - var type_widget = new (self.property.get_any([widget.type])) (self.edit_node_dialog, widget); - var value = _.detect(arch_val[0]['att_list'],function(res) { - return res instanceof Array? _.include(res, widget.name): false; - }); - - value = value instanceof Array ? value[1] : value; - self.edit_node_dialog.$element.find('table[id=rec_table]').append('<tr><td align="right">' + widget.string + ':</td>' + type_widget.render() + '</tr>'); - type_widget.start(); - type_widget.set_value(value); - self.edit_widget.push(type_widget); - }); - }); - }, - //for getting groups - on_groups: function(properties){ - var self = this, - def = $.Deferred(); - if (!_.include(properties, 'groups')) { - self.groups = false; - def.resolve(); - } - var group_ids = [], group_names = {}, groups = []; - var res_groups = new instance.web.DataSetSearch(this,'res.groups', null, null), - model_data = new instance.web.DataSetSearch(self,'ir.model.data', null, null); - res_groups.read_slice([], {}).done(function (res_grp) { - _.each(res_grp, function (res) { - var key = res.id; - group_names[key]=res.full_name; - group_ids.push(res.id); - }); - model_data.read_slice([], {domain: [ - ['res_id', 'in', group_ids], - ['model', '=', 'res.groups'] - ]}).done(function (model_grp) { - _.each(model_grp, function (res_group) { - groups.push([res_group.module + "." + res_group.name, group_names[res_group.res_id]]); - }); - self.groups = groups; - def.resolve(); - }); - }); - return def.promise(); - }, - on_add_node: function(properties, fields){ - var self = this; - var render_list = [{'name': 'node_type','selection': _.keys(_CHILDREN).sort(), 'value': 'field', 'string': 'Node Type','type': 'selection'}, - {'name': 'field_value','selection': fields, 'value': false, 'string': '','type': 'selection'}, - {'name': 'position','selection': ['After','Before','Inside'], 'value': false, 'string': 'Position','type': 'selection'}]; - this.add_widget = []; - this.add_node_dialog = new instance.web.Dialog(this,{ - title: _t("Properties"), - width: 450, - buttons: [ - {text: _t("Update"), click: function() { - var check_add_node = true, values = {}; - _.each(self.add_widget, function(widget) { - values[widget.name] = widget.get_value() || false; - }); - (values.position == "Inside")? - check_add_node =(_.include(_CHILDREN[properties[0]],values.node_type))?true:false: - check_add_node =(_.include(_CHILDREN[properties[1]],values.node_type))?true:false; - if(values.node_type == "field" && check_add_node ) - {check_add_node = (values.field_value != " ")?true:false; - } - if(check_add_node){ - var tag = (values.node_type == "field")? - _.str.sprintf("<%s name='%s'> </%s>",values.node_type,values.field_value,values.node_type): - _.str.sprintf("<%s> </%s>",values.node_type,values.node_type); - self.do_save_update_arch("add_node", [tag, values.position]); - } else { - alert("Can't Update View"); - } - }}, - {text: _t("Cancel"), click: function() { self.add_node_dialog.close(); }} - ] - }).open(); - this.add_node_dialog.$element.append('<table id="rec_table" style="width:420px" class="oe_form"><tbody><tr></tbody></table>'); - var table_selector = self.add_node_dialog.$element.find('table[id=rec_table] tbody'); - _.each(render_list, function(node) { - type_widget = new (self.property.get_any([node.type])) (self.add_node_dialog, node); - if (node.name == "position") { - table_selector.append('</tr><tr><td align="right" width="100px">' + node.string + '</td>' + type_widget.render() + '</tr>'); - } else { - table_selector.append('<td align="right">' + node.string + '</td>' + type_widget.render() ); - if (node.name == "field_value") { - table_selector.append('<td id="new_field" align="right" width="100px"> <button>New Field</button></td>'); - } - } - type_widget.start(); - type_widget.set_value(node.value); - self.add_widget.push(type_widget); - }); - table_selector.find("td[id^=]").attr("width","100px"); - self.add_node_dialog.$element.find('#new_field').click(function() { - model_data = new instance.web.DataSetSearch(self,'ir.model', null, null); - model_data.read_slice([], {domain: [['model','=', self.model]]}).then(function(result) { - self.render_new_field(result[0]); - }); - }); - }, - render_new_field :function( result ) { - var self = this; - var action = { - context: {'default_model_id': result.id, 'manual': true, 'module' : result.modules}, - res_model: "ir.model.fields", - views: [[false, 'form']], - type: 'ir.actions.act_window', - target: "new", - flags: { - action_buttons: true - } - }; - var action_manager = new instance.web.ActionManager(self); - $.when(action_manager.do_action(action)).then(function() { - var controller = action_manager.dialog_viewmanager.views['form'].controller; - controller.on_button_cancel.add_last(function(){ - action_manager.destroy() - }); - controller.do_save.add_last(function(){ - action_manager.destroy(); - var value =controller.fields.name.get('value'); - self.add_node_dialog.$element.find('select[id=field_value]').append($("<option selected></option>").attr("value",value).text(value)); - _.detect(self.add_widget,function(widget){ - widget.name == "field_value"? widget.selection.push(value): false; - }); - }); - }); - } -}); -instance.web.ViewEditor.Field = instance.web.Class.extend({ - init: function(view, widget) { - this.$element = view.$element; - this.dirty = false; - this.name = widget.name; - this.selection = widget.selection || []; - this.required = widget.required || false; - this.string = widget.string || ""; - this.type = widget.type; - this.is_invalid = false; - }, - start: function () { - this.update_dom(); - }, - update_dom: function() { - this.$element.find("td[id=" + this.name + "]").toggleClass('invalid', this.is_invalid); - this.$element.find("td[id=" + this.name + "]").toggleClass('required', this.required); - }, - on_ui_change: function() { - this.validate(); - this.dirty = true; - this.update_dom(); - }, - validate: function() { - this.is_invalid = false; - try { - var value = instance.web.parse_value(this.get_value(), this, ''); - this.is_invalid = this.required && value === ''; - } catch(e) { - this.is_invalid = true; - } - }, - render: function() { - return _.str.sprintf("<td id = %s>%s</td>", this.name, QWeb.render(this.template, {widget: this})) - } -}); -instance.web.ViewEditor.FieldBoolean = instance.web.ViewEditor.Field.extend({ - template : "vieweditor_boolean", - start: function() { - var self = this; - this._super(); - this.$element.find("input[id="+ self.name+"]").change(function() { - self.on_ui_change(); - }); - }, - set_value: function(value) { - if (value) { - this.$element.find("input[id=" + this.name+ "]").attr('checked', true); - } - }, - get_value: function() { - return this.$element.find("input[id=" + this.name + "]").is(':checked')? "1" : null; - } -}); -instance.web.ViewEditor.FieldChar = instance.web.ViewEditor.Field.extend({ - template : "vieweditor_char", - start: function () { - var self = this; - this._super(); - this.$element.find("input[id="+ this.name+"]").css('width','100%').change(function() { - self.on_ui_change(); - }); - }, - set_value: function(value) { - this.$element.find("input[id=" + this.name + "]").val(value); - }, - get_value: function() { - return this.$element.find("input[id=" + this.name + "]").val(); - } -}); -instance.web.ViewEditor.FieldSelect = instance.web.ViewEditor.Field.extend({ - template : "vieweditor_selection", - start: function () { - var self = this; - this._super(); - this.$element.find("select[id=" + this.name + "]").css('width', '100%').change(function() { - self.on_ui_change(); - if (self.name == "node_type") { - if (self.get_value() == "field") { - self.$element.find('#new_field').show(); - self.$element.find("select[id=field_value]").show(); - } else { - self.$element.find('#new_field').hide(); - self.$element.find("select[id=field_value]").hide(); - } - } - }); - }, - set_value: function(value) { - var index = 0; - value = value === null? false: value; - for (var i = 0, ii = this.selection.length; i < ii; i++) { - if ((this.selection[i] instanceof Array && this.selection[i][0] === value) || this.selection[i] === value) index = i; - } - this.$element.find("select[id=" + this.name + "]")[0].selectedIndex = index; - }, - get_value: function() { - return this.$element.find("select[id=" + this.name + "]").val(); - } -}); -instance.web.ViewEditor.FieldSelectMulti = instance.web.ViewEditor.FieldSelect.extend({ - start: function () { - this._super(); - this.$element.find("select[id=" + this.name + "]").css('height', '100px').attr("multiple", true); - }, - set_value: function(value) { - var self = this; - self.$element.find("#groups option").attr("selected",false); - if (!value) return false; - _.each(this.selection, function(item) { - if (_.include(value.split(','), item[0])) { - self.$element.find("select[id="+self.name+"] option[value='" + item[0] +"']").attr("selected",1) - } - }); - } -}); -instance.web.ViewEditor.FieldFloat = instance.web.ViewEditor.FieldChar.extend({ -}); - -var _PROPERTIES = { - 'field' : ['name', 'string', 'required', 'readonly','invisible', 'domain', 'context', 'nolabel', 'completion', - 'colspan', 'widget', 'eval', 'ref', 'on_change', 'attrs', 'groups'], - 'form' : ['string', 'col', 'link'], - 'notebook' : ['colspan', 'position', 'groups'], - 'page' : ['string', 'states', 'attrs', 'groups'], - 'group' : ['string', 'col', 'colspan','invisible', 'states', 'attrs', 'groups'], - 'image' : ['filename', 'width', 'height', 'groups'], - 'separator' : ['string', 'colspan', 'groups'], - 'label': ['string', 'align', 'colspan', 'groups'], - 'button': ['name', 'string', 'icon', 'type', 'states', 'readonly', 'special', 'target', 'confirm', 'context', 'attrs', 'colspan', 'groups'], - 'newline' : [], - 'board': ['style'], - 'column' : [], - 'action' : ['name', 'string', 'colspan', 'groups'], - 'tree' : ['string', 'colors', 'editable', 'link', 'limit', 'min_rows', 'fonts'], - 'graph' : ['string', 'type'], - 'calendar' : ['string', 'date_start', 'date_stop', 'date_delay', 'day_length', 'color', 'mode'] -}; -var _CHILDREN = { - 'form': ['notebook', 'group', 'field', 'label', 'button','board', 'newline', 'separator'], - 'tree': ['field'], - 'graph': ['field'], - 'calendar': ['field'], - 'notebook': ['page'], - 'page': ['notebook', 'group', 'field', 'label', 'button', 'newline', 'separator'], - 'group': ['field', 'label', 'button', 'separator', 'newline','group'], - 'board': ['column'], - 'action': [], - 'field': ['form', 'tree', 'graph','field'], - 'label': [], - 'button' : [], - 'newline': [], - 'separator': [] -}; -var _ICONS = ['','STOCK_ABOUT', 'STOCK_ADD', 'STOCK_APPLY', 'STOCK_BOLD', - 'STOCK_CANCEL', 'STOCK_CDROM', 'STOCK_CLEAR', 'STOCK_CLOSE', 'STOCK_COLOR_PICKER', - 'STOCK_CONNECT', 'STOCK_CONVERT', 'STOCK_COPY', 'STOCK_CUT', 'STOCK_DELETE', - 'STOCK_DIALOG_AUTHENTICATION', 'STOCK_DIALOG_ERROR', 'STOCK_DIALOG_INFO', - 'STOCK_DIALOG_QUESTION', 'STOCK_DIALOG_WARNING', 'STOCK_DIRECTORY', 'STOCK_DISCONNECT', - 'STOCK_DND', 'STOCK_DND_MULTIPLE', 'STOCK_EDIT', 'STOCK_EXECUTE', 'STOCK_FILE', - 'STOCK_FIND', 'STOCK_FIND_AND_REPLACE', 'STOCK_FLOPPY', 'STOCK_GOTO_BOTTOM', - 'STOCK_GOTO_FIRST', 'STOCK_GOTO_LAST', 'STOCK_GOTO_TOP', 'STOCK_GO_BACK', - 'STOCK_GO_DOWN', 'STOCK_GO_FORWARD', 'STOCK_GO_UP', 'STOCK_HARDDISK', - 'STOCK_HELP', 'STOCK_HOME', 'STOCK_INDENT', 'STOCK_INDEX', 'STOCK_ITALIC', - 'STOCK_JUMP_TO', 'STOCK_JUSTIFY_CENTER', 'STOCK_JUSTIFY_FILL', - 'STOCK_JUSTIFY_LEFT', 'STOCK_JUSTIFY_RIGHT', 'STOCK_MEDIA_FORWARD', - 'STOCK_MEDIA_NEXT', 'STOCK_MEDIA_PAUSE', 'STOCK_MEDIA_PLAY', - 'STOCK_MEDIA_PREVIOUS', 'STOCK_MEDIA_RECORD', 'STOCK_MEDIA_REWIND', - 'STOCK_MEDIA_STOP', 'STOCK_MISSING_IMAGE', 'STOCK_NETWORK', 'STOCK_NEW', - 'STOCK_NO', 'STOCK_OK', 'STOCK_OPEN', 'STOCK_PASTE', 'STOCK_PREFERENCES', - 'STOCK_PRINT', 'STOCK_PRINT_PREVIEW', 'STOCK_PROPERTIES', 'STOCK_QUIT', - 'STOCK_REDO', 'STOCK_REFRESH', 'STOCK_REMOVE', 'STOCK_REVERT_TO_SAVED', - 'STOCK_SAVE', 'STOCK_SAVE_AS', 'STOCK_SELECT_COLOR', 'STOCK_SELECT_FONT', - 'STOCK_SORT_ASCENDING', 'STOCK_SORT_DESCENDING', 'STOCK_SPELL_CHECK', - 'STOCK_STOP', 'STOCK_STRIKETHROUGH', 'STOCK_UNDELETE', 'STOCK_UNDERLINE', - 'STOCK_UNDO', 'STOCK_UNINDENT', 'STOCK_YES', 'STOCK_ZOOM_100', - 'STOCK_ZOOM_FIT', 'STOCK_ZOOM_IN', 'STOCK_ZOOM_OUT', - 'terp-account', 'terp-crm', 'terp-mrp', 'terp-product', 'terp-purchase', - 'terp-sale', 'terp-tools', 'terp-administration', 'terp-hr', 'terp-partner', - 'terp-project', 'terp-report', 'terp-stock', 'terp-calendar', 'terp-graph' -]; -instance.web.ViewEditor.property_widget = new instance.web.Registry({ - 'boolean' : 'instance.web.ViewEditor.FieldBoolean', - 'selection_multi' : 'instance.web.ViewEditor.FieldSelectMulti', - 'selection' : 'instance.web.ViewEditor.FieldSelect', - 'char' : 'instance.web.ViewEditor.FieldChar', - 'float' : 'instance.web.ViewEditor.FieldFloat' -}); -}; diff --git a/addons/web/static/src/js/view_form.js b/addons/web/static/src/js/view_form.js index 64381a66218..69e8db19fd3 100644 --- a/addons/web/static/src/js/view_form.js +++ b/addons/web/static/src/js/view_form.js @@ -9,13 +9,22 @@ instance.web.form = {}; /** * Interface implemented by the form view or any other object * able to provide the features necessary for the fields to work. - * + * * Properties: * - display_invalid_fields : if true, all fields where is_valid() return true should * be displayed as invalid. + * - actual_mode : the current mode of the field manager. Can be "view", "edit" or "create". * Events: * - view_content_has_changed : when the values of the fields have changed. When * this event is triggered all fields should reprocess their modifiers. + * - field_changed:<field_name> : when the value of a field change, an event is triggered + * named "field_changed:<field_name>" with <field_name> replaced by the name of the field. + * This event is not related to the on_change mechanism of OpenERP and is always called + * when the value of a field is setted or changed. This event is only triggered when the + * value of the field is syntactically valid, but it can be triggered when the value + * is sematically invalid (ie, when a required field is false). It is possible that an event + * about a precise field is never triggered even if that field exists in the view, in that + * case the value of the field is assumed to be false. */ instance.web.form.FieldManagerMixin = { /** @@ -23,17 +32,27 @@ instance.web.form.FieldManagerMixin = { */ get_field: function(field_name) {}, /** - * Called by the field when the translate button is clicked. + * Returns the current value of a field present in the view. See the get_value() method + * method in FieldInterface for further information. */ - open_translate_dialog: function(field) {}, + get_field_value: function(field_name) {}, /** - * Returns true when the view is in create mode. - */ - is_create_mode: function() {}, + Gives new values for the fields contained in the view. The new values could not be setted + right after the call to this method. Setting new values can trigger on_changes. + + @param (dict) values A dictonnary with key = field name and value = new value. + @return (Deferred) Is resolved after all the values are setted. + */ + set_values: function(values) {}, }; instance.web.views.add('form', 'instance.web.FormView'); -instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form.FieldManagerMixin, { +/** + * Properties: + * - actual_mode: always "view", "edit" or "create". Read-only property. Determines + * the mode used by the view. + */ +instance.web.FormView = instance.web.View.extend(instance.web.form.FieldManagerMixin, { /** * Indicates that this view is not searchable, and thus that no search * view should be displayed (if there is one active). @@ -55,6 +74,7 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. * @property {instance.web.Registry} registry=instance.web.form.widgets widgets registry for this form view instance */ init: function(parent, dataset, view_id, options) { + var self = this; this._super(parent); this.set_default_options(options); this.dataset = dataset; @@ -68,23 +88,43 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. this.default_focus_button = null; this.fields_registry = instance.web.form.widgets; this.tags_registry = instance.web.form.tags; + this.widgets_registry = instance.web.form.custom_widgets; this.has_been_loaded = $.Deferred(); this.translatable_fields = []; _.defaults(this.options, { "not_interactible_on_create": false, "initial_mode": "view", + "disable_autofocus": false, }); this.is_initialized = $.Deferred(); this.mutating_mutex = new $.Mutex(); this.on_change_mutex = new $.Mutex(); this.reload_mutex = new $.Mutex(); - this.rendering_engine = new instance.web.form.FormRenderingEngineReadonly(this); + this.__clicked_inside = false; + this.__blur_timeout = null; + this.rendering_engine = new instance.web.form.FormRenderingEngine(this); this.qweb = null; // A QWeb instance will be created if the view is a QWeb template + self.set({actual_mode: self.options.initial_mode}); + this.has_been_loaded.then(function() { + self.on("change:actual_mode", self, self.check_actual_mode); + self.check_actual_mode(); + self.on("change:actual_mode", self, self.init_pager); + self.init_pager(); + }); + instance.web.bus.on('clear_uncommitted_changes', this, function(e) { + if (!this.can_be_discarded()) { + e.preventDefault(); + } + }); }, destroy: function() { _.each(this.get_widgets(), function(w) { + w.off('focused blurred'); w.destroy(); }); + if (this.$el) { + this.$el.off('.formBlur'); + } this._super(); }, on_loaded: function(data) { @@ -100,51 +140,62 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. this.rendering_engine.set_fields_registry(this.fields_registry); this.rendering_engine.set_tags_registry(this.tags_registry); + this.rendering_engine.set_widgets_registry(this.widgets_registry); if (!this.extract_qweb_template(data)) { this.rendering_engine.set_fields_view(data); - var $dest = this.$element.hasClass("oe_form_container") ? this.$element : this.$element.find('.oe_form_container'); + var $dest = this.$el.hasClass("oe_form_container") ? this.$el : this.$el.find('.oe_form_container'); this.rendering_engine.render_to($dest); } + this.$el.on('mousedown.formBlur', function () { + self.__clicked_inside = true; + }); this.$buttons = $(QWeb.render("FormView.buttons", {'widget':self})); if (this.options.$buttons) { this.$buttons.appendTo(this.options.$buttons); } else { - this.$element.find('.oe_form_buttons').replaceWith(this.$buttons); + this.$el.find('.oe_form_buttons').replaceWith(this.$buttons); } this.$buttons.on('click','.oe_form_button_create',this.on_button_create); this.$buttons.on('click','.oe_form_button_edit',this.on_button_edit); this.$buttons.on('click','.oe_form_button_save',this.on_button_save); this.$buttons.on('click','.oe_form_button_cancel',this.on_button_cancel); - this.$pager = $(QWeb.render("FormView.pager", {'widget':self})); - if (this.options.$pager) { - this.$pager.appendTo(this.options.$pager); - } else { - this.$element.find('.oe_form_pager').replaceWith(this.$pager); - } - this.$pager.on('click','a[data-pager-action]',function() { - var action = $(this).data('pager-action'); - self.on_pager_action(action); - }); - - this.$sidebar = this.options.$sidebar || this.$element.find('.oe_form_sidebar'); + this.$sidebar = this.options.$sidebar || this.$el.find('.oe_form_sidebar'); if (!this.sidebar && this.options.$sidebar) { this.sidebar = new instance.web.Sidebar(this); this.sidebar.appendTo(this.$sidebar); - if(this.fields_view.toolbar) { + if (this.fields_view.toolbar) { this.sidebar.add_toolbar(this.fields_view.toolbar); } - this.sidebar.add_items('other', [ - { label: _t('Delete'), callback: self.on_button_delete }, - { label: _t('Duplicate'), callback: self.on_button_duplicate }, - { label: _t('Set Default'), callback: function (item) { self.open_defaults_dialog(); } }, - ]); - } - this.on("change:mode", this, this.switch_mode); - this.set({mode: this.options.initial_mode}); + this.sidebar.add_items('other', _.compact([ + self.is_action_enabled('delete') && { label: _t('Delete'), callback: self.on_button_delete }, + self.is_action_enabled('create') && { label: _t('Duplicate'), callback: self.on_button_duplicate }, + { label: _t('Set Default'), callback: function (item) { self.open_defaults_dialog(); } } + ])); + } + this.has_been_loaded.resolve(); + + // Add bounce effect on button 'Edit' when click on readonly page view. + this.$el.find(".oe_form_group_row,.oe_form_field,label").on('click', function (e) { + if(self.get("actual_mode") == "view") { + var $button = self.options.$buttons.find(".oe_form_button_edit"); + $button.effect('bounce', {distance: 18, times: 5}, 150); + e.stopPropagation(); + instance.web.bus.trigger('click', e); + } + }); + //bounce effect on red button when click on statusbar. + this.$el.find(".oe_form_field_status:not(.oe_form_status_clickable)").on('click', function (e) { + if((self.get("actual_mode") == "view")) { + var $button = self.$el.find(".oe_highlight:not(.oe_form_invisible)").css({'float':'left','clear':'none'}); + $button.effect('bounce', {distance:18, times: 5}, 150); + e.stopPropagation(); + } + }); + return $.when(); }, extract_qweb_template: function(fvg) { @@ -195,8 +246,32 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. this.default_focus_field = null; this.default_focus_button = null; this.translatable_fields = []; - this.$element.find('.oe_form_container').empty(); + this.$el.find('.oe_form_container').empty(); }, + + widgetFocused: function() { + // Clear click flag if used to focus a widget + this.__clicked_inside = false; + if (this.__blur_timeout) { + clearTimeout(this.__blur_timeout); + this.__blur_timeout = null; + } + }, + widgetBlurred: function() { + if (this.__clicked_inside) { + // clicked in an other section of the form (than the currently + // focused widget) => just ignore the blurring entirely? + this.__clicked_inside = false; + return; + } + var self = this; + // clear timeout, if any + this.widgetFocused(); + this.__blur_timeout = setTimeout(function () { + self.trigger('blurred'); + }, 0); + }, + do_load_state: function(state, warm) { if (state.id && this.datarecord.id != state.id) { if (!this.dataset.get_id_index(state.id)) { @@ -208,43 +283,53 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. } } }, + /** + * + * @param {Object} [options] + * @param {Boolean} [mode=undefined] If specified, switch the form to specified mode. Can be "edit" or "view". + * @param {Boolean} [reload=true] whether the form should reload its content on show, or use the currently loaded record + * @return {$.Deferred} + */ do_show: function (options) { var self = this; options = options || {}; if (this.sidebar) { - this.sidebar.$element.show(); + this.sidebar.$el.show(); } if (this.$buttons) { this.$buttons.show(); - this.$buttons.find('.oe_form_button_save').removeClass('oe_form_button_save_dirty'); } - if (this.$pager) { - this.$pager.show(); - } - this.$element.show().css('visibility', 'hidden'); - this.$element.removeClass('oe_form_dirty'); - return this.has_been_loaded.pipe(function() { - var result; - if (self.dataset.index === null) { - // null index means we should start a new record - result = self.on_button_new(); - } else { - result = self.dataset.read_index(_.keys(self.fields_view.fields), { - context : { 'bin_size' : true } - }).pipe(self.on_record_loaded); - } - result.pipe(function() { - if (options.editable) { - self.set({mode: "edit"}); + this.$el.show().css({ + opacity: '0', + filter: 'alpha(opacity = 0)' + }); + this.$el.add(this.$buttons).removeClass('oe_form_dirty'); + + var shown = this.has_been_loaded; + if (options.reload !== false) { + shown = shown.pipe(function() { + if (self.dataset.index === null) { + // null index means we should start a new record + return self.on_button_new(); } - self.$element.css('visibility', 'visible'); + var fields = _.keys(self.fields_view.fields); + fields.push('display_name'); + return self.dataset.read_index(fields, { + context: { 'bin_size': true, 'future_display_name' : true } + }).pipe(self.on_record_loaded); + }); + } + return shown.pipe(function() { + self._actualize_mode(options.mode || self.options.initial_mode); + self.$el.css({ + opacity: '1', + filter: 'alpha(opacity = 100)' }); - return result; }); }, do_hide: function () { if (this.sidebar) { - this.sidebar.$element.hide(); + this.sidebar.$el.hide(); } if (this.$buttons) { this.$buttons.hide(); @@ -257,21 +342,26 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. on_record_loaded: function(record) { var self = this, set_values = []; if (!record) { + this.set({ 'title' : undefined }); this.do_warn("Form", "The record could not be found in the database.", true); return $.Deferred().reject(); } this.datarecord = record; + this._actualize_mode(); + this.set({ 'title' : record.id ? record.display_name : "New" }); if (this.qweb) { this.kill_current_form(); this.rendering_engine.set_fields_view(this.get_fvg_from_qweb(record)); - var $dest = this.$element.hasClass("oe_form_container") ? this.$element : this.$element.find('.oe_form_container'); + var $dest = this.$el.hasClass("oe_form_container") ? this.$el : this.$el.find('.oe_form_container'); this.rendering_engine.render_to($dest); } _(this.fields).each(function (field, f) { field._dirty_flag = false; + field._inhibit_on_change_flag = true; var result = field.set_value(self.datarecord[f] || false); + field._inhibit_on_change_flag = false; set_values.push(result); }); return $.when.apply(null, set_values).pipe(function() { @@ -292,22 +382,34 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. if (self.sidebar) { self.sidebar.do_attachement_update(self.dataset, self.datarecord.id); } - if (self.default_focus_field) { - self.default_focus_field.focus(); - } if (record.id) { self.do_push_state({id:record.id}); + } else { + self.do_push_state({}); } - self.$element.removeClass('oe_form_dirty'); - self.$buttons.find('.oe_form_button_save').removeClass('oe_form_button_save_dirty'); + self.$el.add(self.$buttons).removeClass('oe_form_dirty'); + self.autofocus(); }); }, + /** + * Loads and sets up the default values for the model as the current + * record + * + * @return {$.Deferred} + */ + load_defaults: function () { + var keys = _.keys(this.fields_view.fields); + if (keys.length) { + return this.dataset.default_get(keys) + .pipe(this.on_record_loaded); + } + return this.on_record_loaded({}); + }, on_form_changed: function() { this.trigger("view_content_has_changed"); }, do_notify_change: function() { - this.$element.addClass('oe_form_dirty'); - this.$buttons.find('.oe_form_button_save').addClass('oe_form_button_save_dirty'); + this.$el.add(this.$buttons).addClass('oe_form_dirty'); }, on_pager_action: function(action) { if (this.can_be_discarded()) { @@ -328,11 +430,31 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. this.reload(); } }, + init_pager: function() { + var self = this; + if (this.$pager) + this.$pager.remove(); + if (this.get("actual_mode") === "create") + return; + this.$pager = $(QWeb.render("FormView.pager", {'widget':self})).hide(); + if (this.options.$pager) { + this.$pager.appendTo(this.options.$pager); + } else { + this.$el.find('.oe_form_pager').replaceWith(this.$pager); + } + this.$pager.on('click','a[data-pager-action]',function() { + var action = $(this).data('pager-action'); + self.on_pager_action(action); + }); + this.do_update_pager(); + }, do_update_pager: function(hide_index) { - var index = hide_index ? '-' : this.dataset.index + 1; - this.$pager.find('button').prop('disabled', this.dataset.ids.length < 2).end() - .find('span.oe_pager_index').html(index).end() - .find('span.oe_pager_count').html(this.dataset.ids.length); + this.$pager.toggle(this.dataset.ids.length > 1); + if (hide_index) { + $(".oe_form_pager_state", this.$pager).html(""); + } else { + $(".oe_form_pager_state", this.$pager).html(_.str.sprintf(_t("%d / %d"), this.dataset.index + 1, this.dataset.ids.length)); + } }, parse_on_change: function (on_change, widget) { var self = this; @@ -473,33 +595,18 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. return self.on_processed_onchange(response, processed); } catch(e) { console.error(e); + instance.webclient.crashmanager.on_javascript_exception(e); return $.Deferred().reject(); } }); }, - on_processed_onchange: function(response, processed) { + on_processed_onchange: function(result, processed) { try { - var result = response; if (result.value) { - for (var f in result.value) { - if (!result.value.hasOwnProperty(f)) { continue; } - var field = this.fields[f]; - // If field is not defined in the view, just ignore it - if (field) { - var value_ = result.value[f]; - if (field.get_value() != value_) { - field.set_value(value_); - field._dirty_flag = true; - if (!_.contains(processed, field.name)) { - this.do_onchange(field, processed); - } - } - } - } - this.on_form_changed(); + this._internal_set_values(result.value, processed); } if (!_.isEmpty(result.warning)) { - instance.web.dialog($(QWeb.render("CrashManager.warning", result.warning)), { + instance.web.dialog($(QWeb.render("CrashManager.warning", result.warning)), { title:result.warning.title, modal: true, buttons: [ @@ -509,6 +616,9 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. } if (result.domain) { function edit_domain(node) { + if (typeof node !== "object") { + return; + } var new_domain = result.domain[node.attrs.name]; if (new_domain) { node.attrs.domain = new_domain; @@ -520,63 +630,129 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. return $.Deferred().resolve(); } catch(e) { console.error(e); + instance.webclient.crashmanager.on_javascript_exception(e); return $.Deferred().reject(); } }, - switch_mode: function() { + _internal_set_values: function(values, exclude) { + exclude = exclude || []; + for (var f in values) { + if (!values.hasOwnProperty(f)) { continue; } + var field = this.fields[f]; + // If field is not defined in the view, just ignore it + if (field) { + var value_ = values[f]; + if (field.get_value() != value_) { + field._inhibit_on_change_flag = true; + field.set_value(value_); + field._inhibit_on_change_flag = false; + field._dirty_flag = true; + if (!_.contains(exclude, field.name)) { + this.do_onchange(field, exclude); + } + } + } + } + this.on_form_changed(); + }, + set_values: function(values) { + var self = this; + return this.on_change_mutex.exec(function() { + self._internal_set_values(values); + }); + }, + /** + * Ask the view to switch to view mode if possible. The view may not do it + * if the current record is not yet saved. It will then stay in create mode. + */ + to_view_mode: function() { + this._actualize_mode("view"); + }, + /** + * Ask the view to switch to edit mode if possible. The view may not do it + * if the current record is not yet saved. It will then stay in create mode. + */ + to_edit_mode: function() { + this._actualize_mode("edit"); + }, + /** + * Ask the view to switch to a precise mode if possible. The view is free to + * not respect this command if the state of the dataset is not compatible with + * the new mode. For example, it is not possible to switch to edit mode if + * the current record is not yet saved in database. + * + * @param {string} [new_mode] Can be "edit", "view", "create" or undefined. If + * undefined the view will test the actual mode to check if it is still consistent + * with the dataset state. + */ + _actualize_mode: function(switch_to) { + var mode = switch_to || this.get("actual_mode"); + if (! this.datarecord.id) { + mode = "create"; + } else if (mode === "create") { + mode = "edit"; + } + this.set({actual_mode: mode}); + }, + check_actual_mode: function(source, options) { var self = this; - if(this.get("mode") == "view") { - self.$element.removeClass('oe_form_editable').addClass('oe_form_readonly'); + if(this.get("actual_mode") === "view") { + self.$el.removeClass('oe_form_editable').addClass('oe_form_readonly'); self.$buttons.find('.oe_form_buttons_edit').hide(); self.$buttons.find('.oe_form_buttons_view').show(); self.$sidebar.show(); - _.each(this.fields,function(field){ - field.set({"force_readonly": true}); - }); } else { - self.$element.removeClass('oe_form_readonly').addClass('oe_form_editable'); + self.$el.removeClass('oe_form_readonly').addClass('oe_form_editable'); self.$buttons.find('.oe_form_buttons_edit').show(); self.$buttons.find('.oe_form_buttons_view').hide(); self.$sidebar.hide(); - _.each(this.fields,function(field){ - field.set({"force_readonly": false}); - }); + this.autofocus(); + } + }, + autofocus: function() { + if (this.get("actual_mode") !== "view" && !this.options.disable_autofocus) { + var fields_order = this.fields_order.slice(0); + if (this.default_focus_field) { + fields_order.unshift(this.default_focus_field.name); + } + for (var i = 0; i < fields_order.length; i += 1) { + var field = this.fields[fields_order[i]]; + if (!field.get('effective_invisible') && !field.get('effective_readonly')) { + if (field.focus() !== false) { + break; + } + } + } } }, on_button_save: function() { var self = this; return this.do_save().then(function(result) { - self.set({mode: "view"}); + self.to_view_mode(); }); }, on_button_cancel: function(event) { if (this.can_be_discarded()) { - this.set({mode: "view"}); + if (this.get('actual_mode') === 'create') { + this.trigger('history_back'); + } else { + this.to_view_mode(); + this.on_record_loaded(this.datarecord); + } } return false; }, on_button_new: function() { var self = this; - this.set({mode: "edit"}); - var def = $.Deferred(); - $.when(this.has_been_loaded).then(function() { + this.to_edit_mode(); + return $.when(this.has_been_loaded).pipe(function() { if (self.can_be_discarded()) { - var keys = _.keys(self.fields_view.fields); - if (keys.length) { - self.dataset.default_get(keys).pipe(self.on_record_loaded).then(function() { - def.resolve(); - }); - } else { - self.on_record_loaded({}).then(function() { - def.resolve(); - }); - } + return self.load_defaults(); } }); - return def.promise(); }, on_button_edit: function() { - return this.set({mode: "edit"}); + return this.to_edit_mode(); }, on_button_create: function() { this.dataset.index = null; @@ -589,7 +765,7 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. self.dataset.call('copy', [self.datarecord.id, {}, self.dataset.context]).then(function(new_id) { return self.on_created({ result : new_id }); }).then(function() { - return self.set({mode: "edit"}); + return self.to_edit_mode(); }).then(function() { def.resolve(); }); @@ -614,17 +790,24 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. return def.promise(); }, can_be_discarded: function() { - return !this.$element.is('.oe_form_dirty') || confirm(_t("Warning, the record has been modified, your changes will be discarded.")); + if (this.$el.is('.oe_form_dirty')) { + if (!confirm(_t("Warning, the record has been modified, your changes will be discarded.\n\nAre you sure you want to leave this page ?"))) { + return false; + } + this.$el.removeClass('oe_form_dirty'); + } + return true; }, /** * Triggers saving the form's record. Chooses between creating a new * record or saving an existing one depending on whether the record * already has an id property. * - * @param {Function} success callback on save success - * @param {Boolean} [prepend_on_create=false] if ``do_save`` creates a new record, should that record be inserted at the start of the dataset (by default, records are added at the end) + * @param {Boolean} [prepend_on_create=false] if ``do_save`` creates a new + * record, should that record be inserted at the start of the dataset (by + * default, records are added at the end) */ - do_save: function(success, prepend_on_create) { + do_save: function(prepend_on_create) { var self = this; return this.mutating_mutex.exec(function() { return self.is_initialized.pipe(function() { try { @@ -632,13 +815,14 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. values = {}, first_invalid_field = null; for (var f in self.fields) { + if (!self.fields.hasOwnProperty(f)) { continue; } f = self.fields[f]; if (!f.is_valid()) { form_invalid = true; if (!first_invalid_field) { first_invalid_field = f; } - } else if (f.name !== 'id' && !f.get("readonly") && (!self.datarecord.id || f._dirty_flag)) { + } else if (f.name !== 'id' && (!self.datarecord.id || (!f.get("readonly") && f._dirty_flag))) { // Special case 'id' field, do not save this field // on 'create' : save all non readonly fields // on 'edit' : save non readonly modified fields @@ -647,8 +831,9 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. } if (form_invalid) { self.set({'display_invalid_fields': true}); - for (var f in self.fields) { - self.fields[f]._check_css_flags(); + for (var g in self.fields) { + if (!self.fields.hasOwnProperty(g)) { continue; } + self.fields[g]._check_css_flags(); } first_invalid_field.focus(); self.on_invalid(); @@ -657,21 +842,21 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. self.set({'display_invalid_fields': false}); var save_deferral; if (!self.datarecord.id) { - //console.log("FormView(", self, ") : About to create", values); + // Creation save save_deferral = self.dataset.create(values).pipe(function(r) { - return self.on_created(r, undefined, prepend_on_create); + return self.on_created(r, prepend_on_create); }, null); } else if (_.isEmpty(values) && ! self.force_dirty) { - //console.log("FormView(", self, ") : Nothing to save"); + // Not dirty, noop save save_deferral = $.Deferred().resolve({}).promise(); } else { self.force_dirty = false; - //console.log("FormView(", self, ") : About to save", values); + // Write save save_deferral = self.dataset.write(self.datarecord.id, values, {}).pipe(function(r) { return self.on_saved(r); }, null); } - return save_deferral.then(success); + return save_deferral; } } catch (e) { console.error(e); @@ -680,22 +865,29 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. });}); }, on_invalid: function() { - var msg = "<ul>"; - _.each(this.fields, function(f) { - if (!f.is_valid()) { - msg += "<li>" + (f.node.attrs.string || f.field.string) + "</li>"; - } - }); - msg += "</ul>"; - this.do_warn("The following fields are invalid :", msg); + var warnings = _(this.fields).chain() + .filter(function (f) { return !f.is_valid(); }) + .map(function (f) { + return _.str.sprintf('<li>%s</li>', + _.escape(f.string)); + }).value(); + warnings.unshift('<ul>'); + warnings.push('</ul>'); + this.do_warn("The following fields are invalid :", warnings.join('')); }, - on_saved: function(r, success) { + /** + * Reload the form after saving + * + * @param {Object} r result of the write function. + */ + on_saved: function(r) { if (!r.result) { // should not happen in the server, but may happen for internal purpose return $.Deferred().reject(); } else { return $.when(this.reload()).pipe(function () { - return $.when(r).then(success); }, null); + return r; + }); } }, /** @@ -708,10 +900,10 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. * * Updates the pager and sidebar displays * * @param {Object} r - * @param {Function} success callback to execute after having updated the dataset - * @param {Boolean} [prepend_on_create=false] adds the newly created record at the beginning of the dataset instead of the end + * @param {Boolean} [prepend_on_create=false] adds the newly created record + * at the beginning of the dataset instead of the end */ - on_created: function(r, success, prepend_on_create) { + on_created: function(r, prepend_on_create) { if (!r.result) { // should not happen in the server, but may happen for internal purpose return $.Deferred().reject(); @@ -728,9 +920,10 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. if (this.sidebar) { this.sidebar.do_attachement_update(this.dataset, this.datarecord.id); } - //instance.log("The record has been created with id #" + this.datarecord.id); - this.reload(); - return $.when(_.extend(r, {created: true})).then(success); + //openerp.log("The record has been created with id #" + this.datarecord.id); + return $.when(this.reload()).pipe(function () { + return _.extend(r, {created: true}); + }); } }, on_action: function (action) { @@ -746,8 +939,10 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. if (self.dataset.index == null || self.dataset.index < 0) { return $.when(self.on_button_new()); } else { - return self.dataset.read_index(_.keys(self.fields_view.fields), { - context : { 'bin_size' : true } + var fields = _.keys(self.fields_view.fields); + fields.push('display_name'); + return self.dataset.read_index(fields, { + context : { 'bin_size' : true, 'future_display_name' : true } }).pipe(self.on_record_loaded); } }); @@ -758,15 +953,15 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. }); }, get_fields_values: function(blacklist) { - blacklist = blacklist || []; + blacklist = blacklist || []; var values = {}; var ids = this.get_selected_ids(); values["id"] = ids.length > 0 ? ids[0] : false; _.each(this.fields, function(value_, key) { - if (_.include(blacklist, key)) - return; - var val = value_.get_value(); - values[key] = val; + if (_.include(blacklist, key)) { + return; + } + values[key] = value_.get_value(); }); return values; }, @@ -781,6 +976,15 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. return self.dataset.parent_view.recursive_save(); }); }, + recursive_reload: function() { + var self = this; + var pre = $.when(); + if (self.dataset.parent_view) + pre = self.dataset.parent_view.recursive_reload(); + return pre.pipe(function() { + return self.reload(); + }); + }, is_dirty: function() { return _.any(this.fields, function (value_) { return value_._dirty_flag; @@ -811,21 +1015,22 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. || field.get('invisible') || field.get("readonly") || field.field.type === 'one2many' - || field.field.type === 'many2many') { + || field.field.type === 'many2many' + || field.field.type === 'binary') { return false; } var displayed = value; - switch (field.field.type) { - case 'selection': + if (field instanceof instance.web.form.FieldSelection) { displayed = _(field.values).find(function (option) { return option[0] === value; })[1]; - break; + } else if (field instanceof instance.web.form.FieldMany2One) { + displayed = field.get_displayed(); } return { name: name, - string: field.node.attrs.string || field.field.string, + string: field.string, value: value, displayed: displayed, // convert undefined to false @@ -848,14 +1053,14 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. buttons: [ {text: _t("Close"), click: function () { d.close(); }}, {text: _t("Save default"), click: function () { - var $defaults = d.$element.find('#formview_default_fields'); + var $defaults = d.$el.find('#formview_default_fields'); var field_to_set = $defaults.val(); if (!field_to_set) { $defaults.parent().addClass('oe_form_invalid'); return; } - var condition = d.$element.find('#formview_default_conditions').val(), - all_users = d.$element.find('#formview_default_all').is(':checked'); + var condition = d.$el.find('#formview_default_conditions').val(), + all_users = d.$el.find('#formview_default_all').is(':checked'); new instance.web.DataSet(self, 'ir.values').call( 'set_default', [ self.dataset.model, @@ -874,10 +1079,22 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. register_field: function(field, name) { this.fields[name] = field; this.fields_order.push(name); + if (JSON.parse(field.node.attrs.default_focus || "0")) { + this.default_focus_field = field; + } + + field.on('focused', null, this.proxy('widgetFocused')) + .on('blurred', null, this.proxy('widgetBlurred')); if (this.get_field(name).translate) { this.translatable_fields.push(field); } field.on('changed_value', this, function() { + if (field.is_syntax_valid()) { + this.trigger('field_changed:' + name); + } + if (field._inhibit_on_change_flag) { + return; + } field._dirty_flag = true; if (field.is_syntax_valid()) { this.do_onchange(field); @@ -889,10 +1106,10 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. get_field: function(field_name) { return this.fields_view.fields[field_name]; }, - is_create_mode: function() { - return !this.datarecord.id; + get_field_value: function(field_name) { + return this.fields[field_name].get_value(); }, -})); +}); /** * Interface to be implemented by rendering engines for the form view. @@ -900,12 +1117,12 @@ instance.web.FormView = instance.web.View.extend(_.extend({}, instance.web.form. instance.web.form.FormRenderingEngineInterface = instance.web.Class.extend({ set_fields_view: function(fields_view) {}, set_fields_registry: function(fields_registry) {}, - render_to: function($element) {}, + render_to: function($el) {}, }); /** * Default rendering engine for the form view. - * + * * It is necessary to set the view using set_view() before usage. */ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInterface.extend({ @@ -914,6 +1131,10 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt }, set_fields_view: function(fvg) { this.fvg = fvg; + this.version = parseFloat(this.fvg.arch.attrs.version); + if (isNaN(this.version)) { + this.version = 6.1; + } }, set_tags_registry: function(tags_registry) { this.tags_registry = tags_registry; @@ -921,6 +1142,20 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt set_fields_registry: function(fields_registry) { this.fields_registry = fields_registry; }, + set_widgets_registry: function(widgets_registry) { + this.widgets_registry = widgets_registry; + }, + // Backward compatibility tools, current default version: v6.1 + process_version: function() { + if (this.version < 7.0) { + this.$form.find('form:first').wrapInner('<group col="4"/>'); + this.$form.find('page').each(function() { + if (!$(this).parents('field').length) { + $(this).wrapInner('<group col="4"/>'); + } + }); + } + }, render_to: function($target) { var self = this; this.$target = $target; @@ -930,8 +1165,11 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt var xml = instance.web.json_node_to_xml(this.fvg.arch); this.$form = $('<div class="oe_form">' + xml + '</div>'); + this.process_version(); + this.fields_to_init = []; this.tags_to_init = []; + this.widgets_to_init = []; this.labels = {}; this.process(this.$form); @@ -961,6 +1199,12 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt var w = new (obj)(self.view, instance.web.xml_to_json($elem[0])); w.replace($elem); }); + _.each(this.widgets_to_init, function($elem) { + var widget_type = $elem.attr("type"); + var obj = self.widgets_registry.get_object(widget_type); + var w = new (obj)(self.view, instance.web.xml_to_json($elem[0])); + w.replace($elem); + }); // TODO: return a deferred }, render_element: function(template /* dictionaries */) { @@ -1003,6 +1247,10 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt return $tag; } }, + process_widget: function($widget) { + this.widgets_to_init.push($widget); + return $widget; + }, process_sheet: function($sheet) { var $new_sheet = this.render_element('FormRenderingSheet', $sheet.getAttributes()); this.handle_common_properties($new_sheet, $sheet); @@ -1051,7 +1299,7 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt if (found) return; - $label = $('<label/>').attr({ + var $label = $('<label/>').attr({ 'for' : name, "modifiers": JSON.stringify({invisible: field_modifiers.invisible}), "string": $field.attr('string'), @@ -1116,6 +1364,9 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt if (!$tr || row_cols < colspan) { $tr = $('<tr/>').addClass('oe_form_group_row').appendTo($table); row_cols = cols; + } else if (tagName==='group') { + // When <group> <group/><group/> </group>, we need a spacing between the two groups + $td.addClass('oe_group_right') } row_cols -= colspan; @@ -1141,12 +1392,6 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt $child = $td.children(':first'); switch ($child[0].tagName.toLowerCase()) { case 'separator': - if ($child.attr('orientation') === 'vertical') { - $td.addClass('oe_vertical_separator').attr('width', '1'); - $td.empty(); - row_cols-= $td.attr('colspan') || 1; - total--; - } break; case 'label': if ($child.attr('for')) { @@ -1206,7 +1451,7 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt page_attrs.__page = $new_page; page_attrs.__ic = ic; pages.push(page_attrs); - + $new_page.children().each(function() { self.process($(this)); }); @@ -1221,6 +1466,10 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt if (! page.__ic) return; page.__ic.on("change:effective_invisible", null, function() { + if (!page.__ic.get('effective_invisible')) { + $new_notebook.tabs('select', i); + return; + } var current = $new_notebook.tabs("option", "selected"); if (! pages[current].__ic || ! pages[current].__ic.get("effective_invisible")) return; @@ -1232,7 +1481,7 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt } }); }); - + this.handle_common_properties($new_notebook, $notebook); return $new_notebook; }, @@ -1268,7 +1517,7 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt return $new_label; }, handle_common_properties: function($new_element, $node) { - var str_modifiers = $node.attr("modifiers") || "{}" + var str_modifiers = $node.attr("modifiers") || "{}"; var modifiers = JSON.parse(str_modifiers); var ic = null; if (modifiers.invisible !== undefined) @@ -1279,12 +1528,6 @@ instance.web.form.FormRenderingEngine = instance.web.form.FormRenderingEngineInt }, }); -instance.web.form.FormRenderingEngineReadonly = instance.web.form.FormRenderingEngine.extend({ - alter_field: function(field) { - field.set({"force_readonly": true}); - }, -}); - instance.web.form.FormDialog = instance.web.Dialog.extend({ init: function(parent, options, view_id, dataset) { this._super(parent, options); @@ -1297,7 +1540,7 @@ instance.web.form.FormDialog = instance.web.Dialog.extend({ this.form = new instance.web.FormView(this, this.dataset, this.view_id, { pager: false }); - this.form.appendTo(this.$element); + this.form.appendTo(this.$el); this.form.on_created.add_last(this.on_form_dialog_saved); this.form.on_saved.add_last(this.on_form_dialog_saved); return this; @@ -1389,77 +1632,115 @@ instance.web.form.compute_domain = function(expr, fields) { /** * Must be applied over an class already possessing the PropertiesMixin. * - * Apply the result of the "invisible" domain to this.$element. + * Apply the result of the "invisible" domain to this.$el. */ instance.web.form.InvisibilityChangerMixin = { init: function(field_manager, invisible_domain) { - this._ic_field_manager = field_manager + var self = this; + this._ic_field_manager = field_manager; this._ic_invisible_modifier = invisible_domain; this._ic_field_manager.on("view_content_has_changed", this, function() { - var result = this._ic_invisible_modifier === undefined ? false : - instance.web.form.compute_domain(this._ic_invisible_modifier, this._ic_field_manager.fields); - this.set({"invisible": result}); + var result = self._ic_invisible_modifier === undefined ? false : + instance.web.form.compute_domain( + self._ic_invisible_modifier, + self._ic_field_manager.fields); + self.set({"invisible": result}); }); this.set({invisible: this._ic_invisible_modifier === true, force_invisible: false}); var check = function() { - if (this.get("invisible") || this.get('force_invisible')) { - this.set({"effective_invisible": true}); + if (self.get("invisible") || self.get('force_invisible')) { + self.set({"effective_invisible": true}); } else { - this.set({"effective_invisible": false}); + self.set({"effective_invisible": false}); } }; this.on('change:invisible', this, check); this.on('change:force_invisible', this, check); - _.bind(check, this)(); + check.call(this); }, start: function() { this.on("change:effective_invisible", this, this._check_visibility); this._check_visibility(); }, _check_visibility: function() { - this.$element.toggleClass('oe_form_invisible', this.get("effective_invisible")); + this.$el.toggleClass('oe_form_invisible', this.get("effective_invisible")); }, }; -instance.web.form.InvisibilityChanger = instance.web.Class.extend(_.extend({}, instance.web.PropertiesMixin, instance.web.form.InvisibilityChangerMixin, { - init: function(parent, field_manager, invisible_domain, $element) { +instance.web.form.InvisibilityChanger = instance.web.Class.extend(instance.web.PropertiesMixin, instance.web.form.InvisibilityChangerMixin, { + init: function(parent, field_manager, invisible_domain, $el) { this.setParent(parent); instance.web.PropertiesMixin.init.call(this); instance.web.form.InvisibilityChangerMixin.init.call(this, field_manager, invisible_domain); - this.$element = $element; + this.$el = $el; this.start(); }, -})); +}); + +/** + Base class for all fields, custom widgets and buttons to be displayed in the form view. -instance.web.form.FormWidget = instance.web.Widget.extend(_.extend({}, instance.web.form.InvisibilityChangerMixin, { + Properties: + - effective_readonly: when it is true, the widget is displayed as readonly. Vary depending + the values of the "readonly" property and the "mode" property on the field manager. +*/ +instance.web.form.FormWidget = instance.web.Widget.extend(instance.web.form.InvisibilityChangerMixin, { /** * @constructs instance.web.form.FormWidget * @extends instance.web.Widget * - * @param view + * @param field_manager * @param node */ - init: function(view, node) { - this._super(view); - this.view = view; + init: function(field_manager, node) { + this._super(field_manager); + this.view = field_manager; + this.field_manager = field_manager; this.node = node; this.modifiers = JSON.parse(this.node.attrs.modifiers || '{}'); - instance.web.form.InvisibilityChangerMixin.init.call(this, view, this.modifiers.invisible); + instance.web.form.InvisibilityChangerMixin.init.call(this, this.field_manager, this.modifiers.invisible); + + this.field_manager.on("view_content_has_changed", this, this.process_modifiers); - this.view.on("view_content_has_changed", this, this.process_modifiers); + this.set({required: this.modifiers['required'] === true}); + // some events to make the property "effective_readonly" sync automatically with "readonly" and + // "mode" on field_manager + var self = this; + this.set({"readonly": this.modifiers['readonly'] === true}); + var test_effective_readonly = function() { + self.set({"effective_readonly": self.get("readonly") || self.field_manager.get("actual_mode") === "view"}); + }; + this.on("change:readonly", this, test_effective_readonly); + this.field_manager.on("change:actual_mode", this, test_effective_readonly); + test_effective_readonly.call(this); }, renderElement: function() { this._super(); - this.$element.addClass(this.node.attrs["class"] || ""); + this.$el.addClass(this.node.attrs["class"] || ""); }, destroy: function() { $.fn.tipsy.clear(); this._super.apply(this, arguments); }, + /** + * Sets up blur/focus forwarding from DOM elements to a widget (`this`). + * + * This method is an utility method that is meant to be called by child classes. + * + * @param {jQuery} $e jQuery object of elements to bind focus/blur on + */ + setupFocus: function ($e) { + var self = this; + $e.on({ + focus: function () { self.trigger('focused'); }, + blur: function () { self.trigger('blurred'); } + }); + }, process_modifiers: function() { var compute_domain = instance.web.form.compute_domain; var to_set = {}; for (var a in this.modifiers) { + if (!this.modifiers.hasOwnProperty(a)) { continue; } if (!_.include(["invisible"], a)) { var val = compute_domain(this.modifiers[a], this.view.fields); to_set[a] = val; @@ -1469,7 +1750,7 @@ instance.web.form.FormWidget = instance.web.Widget.extend(_.extend({}, instance. }, do_attach_tooltip: function(widget, trigger, options) { widget = widget || this; - trigger = trigger || this.$element; + trigger = trigger || this.$el; options = _.extend({ delayIn: 500, delayOut: 0, @@ -1480,7 +1761,7 @@ instance.web.form.FormWidget = instance.web.Widget.extend(_.extend({}, instance. template = 'WidgetLabel.tooltip'; } return QWeb.render(template, { - debug: instance.connection.debug, + debug: instance.session.debug, widget: widget })}, gravity: $.fn.tipsy.autoBounds(50, 'nw'), @@ -1501,7 +1782,7 @@ instance.web.form.FormWidget = instance.web.Widget.extend(_.extend({}, instance. parent: {} }); if (a_dataset.parent_view) { - fields_values.parent = a_dataset.parent_view.get_fields_values([a_dataset.child_name]); + fields_values.parent = a_dataset.parent_view.get_fields_values([a_dataset.child_name]); } return fields_values; }, @@ -1519,7 +1800,7 @@ instance.web.form.FormWidget = instance.web.Widget.extend(_.extend({}, instance. if (! v_context) { v_context = (this.field || {}).context || {}; } - + if (v_context.__ref || true) { //TODO: remove true var fields_values = this._build_eval_context(blacklist); v_context = new instance.web.CompoundContext(v_context).set_eval_context(fields_values); @@ -1537,26 +1818,30 @@ instance.web.form.FormWidget = instance.web.Widget.extend(_.extend({}, instance. } return final_domain; } -})); +}); instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({ template: 'WidgetButton', - init: function(view, node) { - this._super(view, node); + init: function(field_manager, node) { + this._super(field_manager, node); this.force_disabled = false; this.string = (this.node.attrs.string || '').replace(/_/g, ''); - if (this.node.attrs.default_focus == '1') { + if (JSON.parse(this.node.attrs.default_focus || "0")) { // TODO fme: provide enter key binding to widgets this.view.default_focus_button = this; } + if (this.node.attrs.icon && (! /\//.test(this.node.attrs.icon))) { + this.node.attrs.icon = '/web/static/src/img/icons/' + this.node.attrs.icon + '.png'; + } this.view.on('view_content_has_changed', this, this.check_disable); }, start: function() { this._super.apply(this, arguments); - this.$element.click(this.on_click); - if (this.node.attrs.help || instance.connection.debug) { + this.$el.click(this.on_click); + if (this.node.attrs.help || instance.session.debug) { this.do_attach_tooltip(); } + this.setupFocus(this.$el); }, on_click: function() { var self = this; @@ -1605,37 +1890,30 @@ instance.web.form.WidgetButton = instance.web.form.FormWidget.extend({ on_confirmed: function() { var self = this; - var context = this.node.attrs.context; - if (context && context.__ref) { - context = new instance.web.CompoundContext(context); - context.set_eval_context(this._build_eval_context()); - } + var context = this.build_context(); return this.view.do_execute_action( _.extend({}, this.node.attrs, {context: context}), this.view.dataset, this.view.datarecord.id, function () { - self.view.reload(); + self.view.recursive_reload(); }); }, check_disable: function() { var disabled = (this.force_disabled || !this.view.is_interactible_record()); - this.$element.prop('disabled', disabled); - this.$element.css('color', disabled ? 'grey' : ''); + this.$el.prop('disabled', disabled); + this.$el.css('color', disabled ? 'grey' : ''); } }); /** * Interface to be implemented by fields. - * - * Properties: - * - readonly: boolean. If set to true the field should appear in readonly mode. - * - force_readonly: boolean, When it is true, the field should always appear - * in read only mode, no matter what the value of the "readonly" property can be. + * * Events: - * - changed_value: triggered to inform the view to check on_changes - * + * - changed_value: triggered when the value of the field has changed. This can be due + * to a user interaction or a call to set_value(). + * */ -instance.web.form.FieldMixin = { +instance.web.form.FieldInterface = { /** * Constructor takes 2 arguments: * - field_manager: Implements FieldManagerMixin @@ -1644,14 +1922,14 @@ instance.web.form.FieldMixin = { init: function(field_manager, node) {}, /** * Called by the form view to indicate the value of the field. - * + * * set_value() may return an object that can be passed to $.when() that represents the moment when * the field has finished all operations necessary before the user can effectively use the widget. - * + * * Multiple calls to set_value() can occur at any time and must be handled correctly by the implementation, * regardless of any asynchronous operation currently running and the status of any promise that a * previous call to set_value() could have returned. - * + * * set_value() must be able, at any moment, to handle the syntax returned by the "read" method of the * osv class in the OpenERP server as well as the syntax used by the set_value() (see below). It must * also be able to handle any other format commonly used in the _defaults key on the models in the addons @@ -1661,19 +1939,19 @@ instance.web.form.FieldMixin = { set_value: function(value_) {}, /** * Get the current value of the widget. - * - * Must always return a syntaxically correct value to be passed to the "write" method of the osv class in + * + * Must always return a syntactically correct value to be passed to the "write" method of the osv class in * the OpenERP server, although it is not assumed to respect the constraints applied to the field. - * For example if the field is marqued as "required", a call to get_value() can return false. - * + * For example if the field is marked as "required", a call to get_value() can return false. + * * get_value() can also be called *before* a call to set_value() and, in that case, is supposed to - * return a defaut value according to the type of field. - * + * return a default value according to the type of field. + * * This method is always assumed to perform synchronously, it can not return a promise. - * + * * If there was no user interaction to modify the value of the field, it is always assumed that * get_value() return the same semantic value than the one passed in the last call to set_value(), - * altough the syntax can be different. This can be the case for type of fields that have a different + * although the syntax can be different. This can be the case for type of fields that have a different * syntax for "read" and "write" (example: m2o: set_value([0, "Administrator"]), get_value() => 0). */ get_value: function() {}, @@ -1688,28 +1966,30 @@ instance.web.form.FieldMixin = { */ is_valid: function() {}, /** - * Returns true if the field holds a value which is syntaxically correct, ignoring + * Returns true if the field holds a value which is syntactically correct, ignoring * the potential semantic restrictions applied to the field. */ is_syntax_valid: function() {}, /** - * Must set the focus on the field. + * Must set the focus on the field. Return false if field is not focusable. */ focus: function() {}, + /** + * Called when the translate button is clicked. + */ + on_translate: function() {}, }; /** - * Abstract class for classes implementing FieldMixin. - * + * Abstract class for classes implementing FieldInterface. + * * Properties: - * - effective_readonly: when it is true, the widget is displayed as readonly. Vary depending - * the values of the "readonly" property and the "force_readonly" property on the field manager. * - value: useful property to hold the value of the field. By default, set_value() and get_value() * set and retrieve the value property. Changing the value property also triggers automatically * a 'changed_value' event that inform the view to trigger on_changes. - * + * */ -instance.web.form.AbstractField = instance.web.form.FormWidget.extend(_.extend({}, instance.web.form.FieldMixin, { +instance.web.form.AbstractField = instance.web.form.FormWidget.extend(instance.web.form.FieldInterface, { /** * @constructs instance.web.form.AbstractField * @extends instance.web.form.FormWidget @@ -1718,26 +1998,17 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(_.extend({ * @param node */ init: function(field_manager, node) { + var self = this this._super(field_manager, node); - this.field_manager = field_manager; this.name = this.node.attrs.name; - this.set({'value': false}); this.field = this.field_manager.get_field(this.name); - this.set({required: this.modifiers['required'] === true}); - - // some events to make the property "effective_readonly" sync automatically with "readonly" and - // "force_readonly" - this.set({"readonly": this.modifiers['readonly'] === true}); - var test_effective_readonly = function() { - this.set({"effective_readonly": this.get("readonly") || !!this.get("force_readonly")}); - }; - this.on("change:readonly", this, test_effective_readonly); - this.on("change:force_readonly", this, test_effective_readonly); - _.bind(test_effective_readonly, this)(); - + this.widget = this.node.attrs.widget; + this.string = this.node.attrs.string || this.field.string || this.name; + this.options = JSON.parse(this.node.attrs.options || '{}'); + this.set({'value': false}); + this.on("change:value", this, function() { - if (! this._inhibit_on_change) - this.trigger('changed_value'); + this.trigger('changed_value'); this._check_css_flags(); }); }, @@ -1745,14 +2016,12 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(_.extend({ var self = this; this._super(); if (this.field.translate) { - this.$element.addClass('oe_form_field_translatable'); - this.$element.find('.oe_field_translate').click(_.bind(function() { - this.field_manager.open_translate_dialog(this); - }, this)); + this.$el.addClass('oe_form_field_translatable'); + this.$el.find('.oe_field_translate').click(this.on_translate); } - this.$label = this.view.$element.find('label[for=' + this.id_for_label + ']'); - if (instance.connection.debug) { - this.do_attach_tooltip(this, this.$label[0] || this.$element); + this.$label = this.view.$el.find('label[for=' + this.id_for_label + ']'); + if (instance.session.debug) { + this.do_attach_tooltip(this, this.$label[0] || this.$el); this.$label.off('dblclick').on('dblclick', function() { console.log("Field '%s' of type '%s' in View: %o", self.name, (self.node.attrs.widget || self.field.type), self.view); window.w = self; @@ -1771,18 +2040,16 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(_.extend({ * Private. Do not use. */ _set_required: function() { - this.$element.toggleClass('oe_form_required', this.get("required")); + this.$el.toggleClass('oe_form_required', this.get("required")); }, set_value: function(value_) { - this._inhibit_on_change = true; this.set({'value': value_}); - this._inhibit_on_change = false; }, get_value: function() { return this.get('value'); }, is_valid: function() { - return this.is_syntax_valid() && (! this.get('required') || ! this.is_false()); + return this.is_syntax_valid() && !(this.get('required') && this.is_false()); }, is_syntax_valid: function() { return true; @@ -1794,46 +2061,36 @@ instance.web.form.AbstractField = instance.web.form.FormWidget.extend(_.extend({ is_false: function() { return this.get('value') === false; }, - _check_css_flags: function(show_invalid) { + _check_css_flags: function() { if (this.field.translate) { - this.$element.find('.oe_field_translate').toggle(!this.field_manager.is_create_mode()); + this.$el.find('.oe_field_translate').toggle(this.field_manager.get('actual_mode') !== "create"); } if (!this.disable_utility_classes) { if (this.field_manager.get('display_invalid_fields')) { - this.$element.toggleClass('oe_form_invalid', !this.is_valid()); + this.$el.toggleClass('oe_form_invalid', !this.is_valid()); } } }, focus: function() { - }, - /** - * Utility method to focus an element, but only after a small amount of time. - */ - delay_focus: function($elem) { - setTimeout(function() { - $elem.focus(); - }, 50); - }, - /** - * Utility method to get the widget options defined in the field xml description. - */ - get_definition_options: function() { - if (!this.definition_options) { - var str = this.node.attrs.options || '{}'; - this.definition_options = JSON.parse(str); - } - return this.definition_options; + return false; }, set_input_id: function(id) { this.id_for_label = id; }, -})); + on_translate: function() { + var self = this; + var trans = new instance.web.DataSet(this, 'ir.translation'); + return trans.call_button('translate_fields', [this.view.dataset.model, this.view.datarecord.id, this.name, this.view.dataset.get_context()]).then(function(r) { + self.do_action(r.result); + }); + }, +}); /** - * A mixin to apply on any field that has to completely re-render when its readonly state + * A mixin to apply on any FormWidget that has to completely re-render when its readonly state * switch. */ -instance.web.form.ReinitializeFieldMixin = { +instance.web.form.ReinitializeWidgetMixin = { /** * Default implementation of start(), use it or call explicitly initialize_field(). */ @@ -1846,10 +2103,8 @@ instance.web.form.ReinitializeFieldMixin = { this.destroy_content(); this.renderElement(); this.initialize_content(); - this.render_value(); }); this.initialize_content(); - this.render_value(); }, /** * Called to destroy anything that could have been created previously, called before a @@ -1860,13 +2115,27 @@ instance.web.form.ReinitializeFieldMixin = { * Called to initialize the content. */ initialize_content: function() {}, +}; + +/** + * A mixin to apply on any field that has to completely re-render when its readonly state + * switch. + */ +instance.web.form.ReinitializeFieldMixin = _.extend({}, instance.web.form.ReinitializeWidgetMixin, { + initialize_field: function() { + instance.web.form.ReinitializeWidgetMixin.initialize_field.call(this); + this.on("change:effective_readonly", this, function() { + this.render_value(); + }); + this.render_value(); + }, /** * Called to render the value. Should also be explicitly called at the end of a set_value(). */ render_value: function() {}, -}; +}); -instance.web.form.FieldChar = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, { +instance.web.form.FieldChar = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, { template: 'FieldChar', widget_class: 'oe_form_field_char', init: function (field_manager, node) { @@ -1875,9 +2144,11 @@ instance.web.form.FieldChar = instance.web.form.AbstractField.extend(_.extend({} }, initialize_content: function() { var self = this; - this.$element.find('input').change(function() { - self.set({'value': instance.web.parse_value(self.$element.find('input').val(), self)}); + var $input = this.$el.find('input'); + $input.change(function() { + self.set({'value': instance.web.parse_value($input.val(), self)}); }); + this.setupFocus($input); }, set_value: function(value_) { this._super(value_); @@ -1886,18 +2157,18 @@ instance.web.form.FieldChar = instance.web.form.AbstractField.extend(_.extend({} render_value: function() { var show_value = instance.web.format_value(this.get('value'), this, ''); if (!this.get("effective_readonly")) { - this.$element.find('input').val(show_value); + this.$el.find('input').val(show_value); } else { if (this.password) { show_value = new Array(show_value.length + 1).join('*'); } - this.$element.text(show_value); + this.$el.text(show_value); } }, is_syntax_valid: function() { if (!this.get("effective_readonly")) { try { - var value_ = instance.web.parse_value(this.$element.find('input').val(), this, ''); + var value_ = instance.web.parse_value(this.$el.find('input').val(), this, ''); return true; } catch(e) { return false; @@ -1909,27 +2180,29 @@ instance.web.form.FieldChar = instance.web.form.AbstractField.extend(_.extend({} return this.get('value') === '' || this._super(); }, focus: function() { - this.delay_focus(this.$element.find('input:first')); + this.$('input:first').focus(); } -})); +}); instance.web.form.FieldID = instance.web.form.FieldChar.extend({ - + }); instance.web.form.FieldEmail = instance.web.form.FieldChar.extend({ template: 'FieldEmail', initialize_content: function() { this._super(); - this.$element.find('button').click(this.on_button_clicked); + var $button = this.$el.find('button'); + $button.click(this.on_button_clicked); + this.setupFocus($button); }, render_value: function() { if (!this.get("effective_readonly")) { this._super(); } else { - this.$element.find('a') + this.$el.find('a') .attr('href', 'mailto:' + this.get('value')) - .text(this.get('value')); + .text(this.get('value') || ''); } }, on_button_clicked: function() { @@ -1945,7 +2218,9 @@ instance.web.form.FieldUrl = instance.web.form.FieldChar.extend({ template: 'FieldUrl', initialize_content: function() { this._super(); - this.$element.find('button').click(this.on_button_clicked); + var $button = this.$el.find('button'); + $button.click(this.on_button_clicked); + this.setupFocus($button); }, render_value: function() { if (!this.get("effective_readonly")) { @@ -1956,7 +2231,7 @@ instance.web.form.FieldUrl = instance.web.form.FieldChar.extend({ if (!s) { tmp = "http://" + this.get('value'); } - this.$element.find('a').attr('href', tmp).text(tmp); + this.$el.find('a').attr('href', tmp).text(this.get('value') ? tmp : ''); } }, on_button_clicked: function() { @@ -1978,7 +2253,7 @@ instance.web.form.FieldFloat = instance.web.form.FieldChar.extend({ this._super(field_manager, node); this.set({'value': 0}); if (this.node.attrs.digits) { - this.digits = py.eval(node.attrs.digits); + this.digits = this.node.attrs.digits; } else { this.digits = this.field.digits; } @@ -1989,10 +2264,13 @@ instance.web.form.FieldFloat = instance.web.form.FieldChar.extend({ value_ = 0; } this._super.apply(this, [value_]); + }, + focus: function () { + this.$('input:first').select(); } }); -instance.web.DateTimeWidget = instance.web.OldWidget.extend({ +instance.web.DateTimeWidget = instance.web.Widget.extend({ template: "web.datepicker", jqueryui_object: 'datetimepicker', type_of_date: "datetime", @@ -2002,23 +2280,27 @@ instance.web.DateTimeWidget = instance.web.OldWidget.extend({ }, start: function() { var self = this; - this.$input = this.$element.find('input.oe_datepicker_master'); - this.$input_picker = this.$element.find('input.oe_datepicker_container'); + this.$input = this.$el.find('input.oe_datepicker_master'); + this.$input_picker = this.$el.find('input.oe_datepicker_container'); this.$input.change(this.on_change); this.picker({ + onClose: this.on_picker_select, onSelect: this.on_picker_select, changeMonth: true, changeYear: true, showWeek: true, - showButtonPanel: true + showButtonPanel: true, + firstDay: Date.CultureInfo.firstDayOfWeek }); - this.$element.find('img.oe_datepicker_trigger').click(function() { - if (!self.get("effective_readonly") && !self.picker('widget').is(':visible')) { - self.picker('setDate', self.get('value') ? instance.web.auto_str_to_date(self.get('value')) : new Date()); - self.$input_picker.show(); - self.picker('show'); - self.$input_picker.hide(); + this.$el.find('img.oe_datepicker_trigger').click(function() { + if (self.get("effective_readonly") || self.picker('widget').is(':visible')) { + self.$input.focus(); + return; } + self.picker('setDate', self.value ? instance.web.auto_str_to_date(self.value) : new Date()); + self.$input_picker.show(); + self.picker('show'); + self.$input_picker.hide(); }); this.set_readonly(false); this.set({'value': false}); @@ -2028,7 +2310,10 @@ instance.web.DateTimeWidget = instance.web.OldWidget.extend({ }, on_picker_select: function(text, instance_) { var date = this.picker('getDate'); - this.$input.val(date ? this.format_client(date) : '').change(); + this.$input + .val(date ? this.format_client(date) : '') + .change() + .focus(); }, set_value: function(value_) { this.set({'value': value_}); @@ -2044,7 +2329,7 @@ instance.web.DateTimeWidget = instance.web.OldWidget.extend({ set_readonly: function(readonly) { this.readonly = readonly; this.$input.prop('readonly', this.readonly); - this.$element.find('img.oe_datepicker_trigger').toggleClass('oe_input_icon_disabled', readonly); + this.$el.find('img.oe_datepicker_trigger').toggleClass('oe_input_icon_disabled', readonly); }, is_valid_: function() { var value_ = this.$input.val(); @@ -2077,7 +2362,7 @@ instance.web.DateWidget = instance.web.DateTimeWidget.extend({ type_of_date: "date" }); -instance.web.form.FieldDatetime = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, { +instance.web.form.FieldDatetime = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, { template: "FieldDatetime", build_widget: function() { return new instance.web.DateTimeWidget(this); @@ -2094,7 +2379,8 @@ instance.web.form.FieldDatetime = instance.web.form.AbstractField.extend(_.exten this.datewidget.on_change.add_last(_.bind(function() { this.set({'value': this.datewidget.get_value()}); }, this)); - this.datewidget.appendTo(this.$element); + this.datewidget.appendTo(this.$el); + this.setupFocus(this.datewidget.$input); } }, set_value: function(value_) { @@ -2105,7 +2391,7 @@ instance.web.form.FieldDatetime = instance.web.form.AbstractField.extend(_.exten if (!this.get("effective_readonly")) { this.datewidget.set_value(this.get('value')); } else { - this.$element.text(instance.web.format_value(this.get('value'), this, '')); + this.$el.text(instance.web.format_value(this.get('value'), this, '')); } }, is_syntax_valid: function() { @@ -2118,10 +2404,11 @@ instance.web.form.FieldDatetime = instance.web.form.AbstractField.extend(_.exten return this.get('value') === '' || this._super(); }, focus: function() { - if (this.datewidget && this.datewidget.$input) - this.delay_focus(this.datewidget.$input); + if (this.datewidget && this.datewidget.$input) { + this.datewidget.$input.focus(); + } } -})); +}); instance.web.form.FieldDate = instance.web.form.FieldDatetime.extend({ template: "FieldDate", @@ -2130,28 +2417,41 @@ instance.web.form.FieldDate = instance.web.form.FieldDatetime.extend({ } }); -instance.web.form.FieldText = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, { +instance.web.form.FieldText = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, { template: 'FieldText', + init: function (field_manager, node) { + this._super(field_manager, node); + }, initialize_content: function() { - this.$textarea = this.$element.find('textarea'); + var self = this; + this.$textarea = this.$el.find('textarea'); + this.default_height = this.$textarea.css('height'); if (!this.get("effective_readonly")) { this.$textarea.change(_.bind(function() { - this.set({'value': instance.web.parse_value(this.$textarea.val(), this)}); + self.set({'value': instance.web.parse_value(self.$textarea.val(), self)}); }, this)); } else { this.$textarea.attr('disabled', 'disabled'); } + this.$el.keyup(function (e) { + if (e.which === $.ui.keyCode.ENTER) { + e.stopPropagation(); + } + }); + this.setupFocus(this.$textarea); }, set_value: function(value_) { - this._super.apply(this, arguments); + this._super(value_); this.render_value(); + $(window).resize(); }, render_value: function() { var show_value = instance.web.format_value(this.get('value'), this, ''); - this.$textarea.val(show_value); - if (show_value && this.view.options.resize_textareas) { - this.do_resize(this.view.options.resize_textareas); + if (show_value === '') { + this.$textarea.css('height', parseInt(this.default_height)+"px"); } + this.$textarea.val(show_value); + this.$textarea.autosize(); }, is_syntax_valid: function() { if (!this.get("effective_readonly")) { @@ -2167,51 +2467,87 @@ instance.web.form.FieldText = instance.web.form.AbstractField.extend(_.extend({} is_false: function() { return this.get('value') === '' || this._super(); }, - focus: function($element) { - this.delay_focus(this.$textarea); - }, - do_resize: function(max_height) { - max_height = parseInt(max_height, 10); - var $input = this.$textarea, - $div = $('<div style="position: absolute; z-index: 1000; top: 0"/>').width($input.width()), - new_height; - $div.text($input.val()); - _.each('font-family,font-size,white-space'.split(','), function(style) { - $div.css(style, $input.css(style)); - }); - $div.appendTo($('body')); - new_height = $div.height(); - if (new_height < 90) { - new_height = 90; + focus: function($el) { + this.$textarea.focus(); + }, +}); + +/** + * FieldTextHtml Widget + * Intended for FieldText widgets meant to display HTML content. This + * widget will instantiate the CLEditor (see cleditor in static/src/lib) + * To find more information about CLEditor configutation: go to + * http://premiumsoftware.net/cleditor/docs/GettingStarted.html + */ +instance.web.form.FieldTextHtml = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, { + template: 'FieldTextHtml', + init: function() { + this._super.apply(this, arguments); + }, + initialize_content: function() { + var self = this; + if (! this.get("effective_readonly")) { + self._updating_editor = false; + this.$textarea = this.$el.find('textarea'); + var width = ((this.node.attrs || {}).editor_width || 468); + var height = ((this.node.attrs || {}).editor_height || 250); + this.$textarea.cleditor({ + width: width, // width not including margins, borders or padding + height: height, // height not including margins, borders or padding + controls: // controls to add to the toolbar + "bold italic underline strikethrough " + + "| removeformat | bullets numbering | outdent " + + "indent | link unlink | source", + bodyStyle: // style to assign to document body contained within the editor + "margin:4px; font:12px monospace; cursor:text; color:#1F1F1F" + }); + this.$cleditor = this.$textarea.cleditor()[0]; + this.$cleditor.change(function() { + if (! self._updating_editor) { + self.$cleditor.updateTextArea(); + self.set({'value': self.$textarea.val()}); + } + }); } - if (!isNaN(max_height) && new_height > max_height) { - new_height = max_height; + }, + set_value: function(value_) { + this._super.apply(this, arguments); + this.render_value(); + }, + render_value: function() { + if (! this.get("effective_readonly")) { + this.$textarea.val(this.get('value') || ''); + this._updating_editor = true; + this.$cleditor.updateFrame(); + this._updating_editor = false; + } else { + this.$el.html(this.get('value')); } - $div.remove(); - $input.height(new_height); }, -})); +}); instance.web.form.FieldBoolean = instance.web.form.AbstractField.extend({ template: 'FieldBoolean', start: function() { + var self = this; this._super.apply(this, arguments); - this.$checkbox = $("input", this.$element); - this.$element.click(_.bind(function() { + this.$checkbox = $("input", this.$el); + this.setupFocus(this.$checkbox); + this.$el.click(_.bind(function() { this.set({'value': this.$checkbox.is(':checked')}); }, this)); var check_readonly = function() { - this.$checkbox.prop('disabled', this.get("effective_readonly")); + self.$checkbox.prop('disabled', self.get("effective_readonly")); }; this.on("change:effective_readonly", this, check_readonly); - _.bind(check_readonly, this)(); + check_readonly.call(this); }, set_value: function(value_) { this._super.apply(this, arguments); this.$checkbox[0].checked = value_; }, focus: function() { - this.delay_focus(this.$checkbox); + this.$checkbox.focus(); } }); @@ -2219,7 +2555,7 @@ instance.web.form.FieldProgressBar = instance.web.form.AbstractField.extend({ template: 'FieldProgressBar', start: function() { this._super.apply(this, arguments); - this.$element.progressbar({ + this.$el.progressbar({ value: this.get('value'), disabled: this.get("effective_readonly") }); @@ -2231,15 +2567,12 @@ instance.web.form.FieldProgressBar = instance.web.form.AbstractField.extend({ show_value = 0; } var formatted_value = instance.web.format_value(show_value, { type : 'float' }, '0'); - this.$element.progressbar('option', 'value', show_value).find('span').html(formatted_value + '%'); + this.$el.progressbar('option', 'value', show_value).find('span').html(formatted_value + '%'); } }); -instance.web.form.FieldTextXml = instance.web.form.AbstractField.extend({ -// to replace view editor -}); -instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, { +instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, { template: 'FieldSelection', init: function(field_manager, node) { var self = this; @@ -2265,9 +2598,9 @@ instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(_.exte // changing the selected value), takes the action as validating the // row var ischanging = false; - this.$element.find('select') + var $select = this.$el.find('select') .change(_.bind(function() { - this.set({'value': this.values[this.$element.find('select')[0].selectedIndex][0]}); + this.set({'value': this.values[this.$el.find('select')[0].selectedIndex][0]}); }, this)) .change(function () { ischanging = true; }) .click(function () { ischanging = false; }) @@ -2276,6 +2609,7 @@ instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(_.exte e.stopPropagation(); ischanging = false; }); + this.setupFocus($select); }, set_value: function(value_) { value_ = value_ === null ? false : value_; @@ -2289,27 +2623,27 @@ instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(_.exte for (var i = 0, ii = this.values.length; i < ii; i++) { if (this.values[i][0] === this.get('value')) index = i; } - this.$element.find('select')[0].selectedIndex = index; + this.$el.find('select')[0].selectedIndex = index; } else { var self = this; var option = _(this.values) - .detect(function (record) { return record[0] === self.get('value'); }); - this.$element.text(option ? option[1] : this.values[0][1]); + .detect(function (record) { return record[0] === self.get('value'); }); + this.$el.text(option ? option[1] : this.values[0][1]); } }, is_syntax_valid: function() { if (this.get("effective_readonly")) { return true; } - var value_ = this.values[this.$element.find('select')[0].selectedIndex]; + var value_ = this.values[this.$el.find('select')[0].selectedIndex]; return !! value_; }, focus: function() { - this.delay_focus(this.$element.find('select:first')); + this.$el.find('select:first').focus(); } -})); +}); -// jquery autocomplete tweak to allow html +// jquery autocomplete tweak to allow html and classnames (function() { var proto = $.ui.autocomplete.prototype, initSource = proto._initSource; @@ -2336,7 +2670,8 @@ instance.web.form.FieldSelection = instance.web.form.AbstractField.extend(_.exte return $( "<li></li>" ) .data( "item.autocomplete", item ) .append( $( "<a></a>" )[ this.options.html ? "html" : "text" ]( item.label ) ) - .appendTo( ul ); + .appendTo( ul ) + .addClass(item.classname); } }); })(); @@ -2357,43 +2692,56 @@ instance.web.form.CompletionFieldMixin = { var dataset = new instance.web.DataSet(this, this.field.relation, self.build_context()); var blacklist = this.get_search_blacklist(); + this.last_query = search_val; return this.orderer.add(dataset.name_search( search_val, new instance.web.CompoundDomain(self.build_domain(), [["id", "not in", blacklist]]), - 'ilike', this.limit + 1)).pipe(function(data) { + 'ilike', this.limit + 1, self.build_context())).pipe(function(data) { self.last_search = data; // possible selections for the m2o var values = _.map(data, function(x) { + x[1] = x[1].split("\n")[0]; return { label: _.str.escapeHTML(x[1]), - value:x[1], - name:x[1], - id:x[0] + value: x[1], + name: x[1], + id: x[0], }; }); // search more... if more results that max if (values.length > self.limit) { values = values.slice(0, self.limit); - values.push({label: _t("<em>   Search More...</em>"), action: function() { - dataset.name_search(search_val, self.build_domain(), 'ilike' - , false, function(data) { - self._search_create_popup("search", data); - }); - }}); + values.push({ + label: _t("Search More..."), + action: function() { + dataset.name_search(search_val, self.build_domain(), 'ilike', false).then(function(data) { + self._search_create_popup("search", data); + }); + }, + classname: 'oe_m2o_dropdown_option' + }); } // quick create var raw_result = _(data.result).map(function(x) {return x[1];}); if (search_val.length > 0 && !_.include(raw_result, search_val)) { - values.push({label: _.str.sprintf(_t('<em>   Create "<strong>%s</strong>"</em>'), - $('<span />').text(search_val).html()), action: function() { - self._quick_create(search_val); - }}); + values.push({ + label: _.str.sprintf(_t('Create "<strong>%s</strong>"'), + $('<span />').text(search_val).html()), + action: function() { + self._quick_create(search_val); + }, + classname: 'oe_m2o_dropdown_option' + }); } // create... - values.push({label: _t("<em>   Create and Edit...</em>"), action: function() { - self._search_create_popup("form", undefined, {"default_name": search_val}); - }}); + values.push({ + label: _t("Create and Edit..."), + action: function() { + self._search_create_popup("form", undefined, self._create_context(search_val)); + }, + classname: 'oe_m2o_dropdown_option' + }); return values; }); @@ -2404,11 +2752,11 @@ instance.web.form.CompletionFieldMixin = { _quick_create: function(name) { var self = this; var slow_create = function () { - self._search_create_popup("form", undefined, {"default_name": name}); + self._search_create_popup("form", undefined, self._create_context(name)); }; - if (self.get_definition_options().quick_create === undefined || self.get_definition_options().quick_create) { + if (self.options.quick_create === undefined || self.options.quick_create) { new instance.web.DataSet(this, this.field.relation, self.build_context()) - .name_create(name, function(data) { + .name_create(name).then(function(data) { self.add_id(data[0]); }).fail(function(error, event) { event.preventDefault(); @@ -2424,7 +2772,7 @@ instance.web.form.CompletionFieldMixin = { pop.select_element( self.field.relation, { - title: (view === 'search' ? _t("Search: ") : _t("Create: ")) + (this.string || this.name), + title: (view === 'search' ? _t("Search: ") : _t("Create: ")) + this.string, initial_ids: ids ? _.map(ids, function(x) {return x[0]}) : undefined, initial_view: view, disable_multiple_selection: true @@ -2434,16 +2782,51 @@ instance.web.form.CompletionFieldMixin = { ); pop.on_select_elements.add(function(element_ids) { self.add_id(element_ids[0]); + self.focus(); }); }, /** * To implement. */ add_id: function(id) {}, + _create_context: function(name) { + var tmp = {}; + var field = (this.options || {}).create_name_field; + if (field === undefined) + field = "name"; + if (field !== false && name && (this.options || {}).quick_create !== false) + tmp["default_" + field] = name; + return tmp; + }, }; -instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, - instance.web.form.CompletionFieldMixin, { +instance.web.form.M2ODialog = instance.web.Dialog.extend({ + template: "M2ODialog", + init: function(parent) { + this._super(parent, { + title: _.str.sprintf(_t("Add %s"), parent.string), + width: 312, + }); + }, + start: function() { + var self = this; + this.$buttons.html(QWeb.render("M2ODialog.buttons")); + this.$("input").val(this.getParent().last_query); + this.$buttons.find(".oe_form_m2o_qc_button").click(function(){ + self.getParent()._quick_create(self.$("input").val()); + self.destroy(); + }); + this.$buttons.find(".oe_form_m2o_sc_button").click(function(){ + self.getParent()._search_create_popup("form", undefined, self.getParent()._create_context(self.$("input").val())); + self.destroy(); + }); + this.$buttons.find(".oe_form_m2o_cancel_button").click(function(){ + self.destroy(); + }); + }, +}); + +instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(instance.web.form.CompletionFieldMixin, instance.web.form.ReinitializeFieldMixin, { template: "FieldMany2One", init: function(field_manager, node) { this._super(field_manager, node); @@ -2453,6 +2836,7 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(_.exten this.last_search = []; this.floating = false; this.inhibit_on_change = false; + this.current_display = null; }, start: function() { this._super(); @@ -2461,29 +2845,43 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(_.exten this.floating = false; this.render_value(); }); + instance.web.bus.on('click', this, function() { + if (!this.get("effective_readonly") && this.$input && this.$input.autocomplete('widget').is(':visible')) { + this.$input.autocomplete("close"); + } + }); }, initialize_content: function() { if (!this.get("effective_readonly")) this.render_editable(); this.render_value(); }, + init_error_displayer: function() { + // nothing + }, + hide_error_displayer: function() { + // doesn't work + }, + show_error_displayer: function() { + new instance.web.form.M2ODialog(this).open(); + }, render_editable: function() { var self = this; - this.$input = this.$element.find("input"); - - self.$input.tipsy({ - title: function() { - return "No element was selected, you should create or select one from the dropdown list."; - }, - trigger:'manual', - fade: true, + this.$input = this.$el.find("input"); + + this.init_error_displayer(); + + self.$input.on('focus', function() { + self.hide_error_displayer(); }); - - this.$drop_down = this.$element.find(".oe-m2o-drop-down-button"); - this.$follow_button = $(".oe-m2o-cm-button", this.$element); - - this.$follow_button.click(function() { + + this.$drop_down = this.$el.find(".oe_m2o_drop_down_button"); + this.$follow_button = $(".oe_m2o_cm_button", this.$el); + + this.$follow_button.click(function(ev) { + ev.preventDefault(); if (!self.get('value')) { + self.focus(); return; } var pop = new instance.web.form.FormOpenPopup(self.view); @@ -2492,40 +2890,48 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(_.exten self.get("value"), self.build_context(), { - title: _t("Open: ") + (self.string || self.name) + title: _t("Open: ") + self.string } ); pop.on_write_completed.add_last(function() { self.display_value = {}; self.render_value(); + self.focus(); }); }); // some behavior for input - this.$input.keyup(function() { - if (self.$input.val() === "") { - self.set({value: false}); - } else { - self.floating = true; + var input_changed = function() { + if (self.current_display !== self.$input.val()) { + self.current_display = self.$input.val(); + if (self.$input.val() === "") { + self.set({value: false}); + self.floating = false; + } else { + self.floating = true; + } } - }); + }; + this.$input.keydown(input_changed); + this.$input.change(input_changed); this.$drop_down.click(function() { if (self.$input.autocomplete("widget").is(":visible")) { self.$input.autocomplete("close"); + self.$input.focus(); } else { if (self.get("value") && ! self.floating) { self.$input.autocomplete("search", ""); } else { self.$input.autocomplete("search"); } - self.$input.focus(); } }); - var tip_def = $.Deferred(); - var untip_def = $.Deferred(); - var tip_delay = 200; - var tip_duration = 3000; + self.ed_def = $.Deferred(); + self.uned_def = $.Deferred(); + var ed_delay = 200; + var ed_duration = 15000; var anyoneLoosesFocus = function() { + var used = false; if (self.floating) { if (self.last_search.length > 0) { if (self.last_search[0][0] != self.get("value")) { @@ -2533,33 +2939,52 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(_.exten self.display_value["" + self.last_search[0][0]] = self.last_search[0][1]; self.set({value: self.last_search[0][0]}); } else { + used = true; self.render_value(); } } else { + used = true; self.set({value: false}); + self.render_value(); } + self.floating = false; } - if (! self.get("value")) { - tip_def.reject(); - untip_def.reject(); - tip_def = $.Deferred(); - tip_def.then(function() { - self.$input.tipsy("show"); + if (used && self.get("value") === false && ! self.no_ed) { + self.ed_def.reject(); + self.uned_def.reject(); + self.ed_def = $.Deferred(); + self.ed_def.then(function() { + self.show_error_displayer(); }); setTimeout(function() { - tip_def.resolve(); - untip_def.reject(); - untip_def = $.Deferred(); - untip_def.then(function() { - self.$input.tipsy("hide"); + self.ed_def.resolve(); + self.uned_def.reject(); + self.uned_def = $.Deferred(); + self.uned_def.then(function() { + self.hide_error_displayer(); }); - setTimeout(function() {untip_def.resolve();}, tip_duration); - }, tip_delay); + setTimeout(function() {self.uned_def.resolve();}, ed_duration); + }, ed_delay); } else { - tip_def.reject(); + self.no_ed = false; + self.ed_def.reject(); } }; - this.$input.focusout(anyoneLoosesFocus); + var ignore_blur = false; + this.$input.on({ + focusout: anyoneLoosesFocus, + focus: function () { self.trigger('focused'); }, + autocompleteopen: function () { ignore_blur = true; }, + autocompleteclose: function () { ignore_blur = false; }, + blur: function () { + // autocomplete open + if (ignore_blur) { return; } + if (_(self.getChildren()).any(function (child) { + return child instanceof instance.web.form.AbstractFormPopup; + })) { return; } + self.trigger('blurred'); + } + }); var isSelecting = false; // autocomplete @@ -2577,8 +3002,9 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(_.exten self.display_value["" + item.id] = item.name; self.set({value: item.id}); } else if (item.action) { - self.floating = true; item.action(); + // Cancel widget blurring, to avoid form blur event + self.trigger('focused'); return false; } }, @@ -2586,21 +3012,22 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(_.exten e.preventDefault(); }, html: true, - close: anyoneLoosesFocus, + // disabled to solve a bug, but may cause others + //close: anyoneLoosesFocus, minLength: 0, delay: 0 }); this.$input.autocomplete("widget").addClass("openerp"); // used to correct a bug when selecting an element by pushing 'enter' in an editable list this.$input.keyup(function(e) { - if (e.which === 13) { + if (e.which === 13) { // ENTER if (isSelecting) e.stopPropagation(); } isSelecting = false; }); + this.setupFocus(this.$follow_button); }, - render_value: function(no_recurse) { var self = this; if (! this.get("value")) { @@ -2613,8 +3040,8 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(_.exten return; } if (! no_recurse) { - var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.view.dataset.get_context()); - dataset.name_get([self.get("value")], function(data) { + var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.build_context()); + dataset.name_get([self.get("value")]).then(function(data) { self.display_value["" + self.get("value")] = data[0][1]; self.render_value(true); }); @@ -2623,12 +3050,24 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(_.exten display_string: function(str) { var self = this; if (!this.get("effective_readonly")) { - this.$input.val(str); + this.$input.val(str.split("\n")[0]); + this.current_display = this.$input.val(); + if(this.is_false()){ + this.$('.oe_m2o_cm_button').css({'display':'none'}); + } } else { - this.$element.find('a') + var lines = _.escape(str).split("\n"); + var link = ""; + var follow = ""; + link = lines[0]; + follow = _.rest(lines).join("<br />"); + if (follow) + link += "<br />"; + var $link = this.$el.find('.oe_form_uri') .unbind('click') - .text(str) - .click(function () { + .html(link); + if (! this.options.no_open) + $link.click(function () { self.do_action({ type: 'ir.actions.act_window', res_model: self.field.relation, @@ -2639,13 +3078,16 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(_.exten }); return false; }); + $(".oe_form_m2o_follow", this.$el).html(follow); } }, set_value: function(value_) { var self = this; if (value_ instanceof Array) { this.display_value = {}; - this.display_value["" + value_[0]] = value_[1]; + if (! this.options.always_reload) { + this.display_value["" + value_[0]] = value_[1]; + } value_ = value_[0]; } value_ = value_ || false; @@ -2653,6 +3095,9 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(_.exten this._super(value_); this.inhibit_on_change = false; }, + get_displayed: function() { + return this.display_value["" + this.get("value")]; + }, add_id: function(id) { this.display_value = {}; this.set({value: id}); @@ -2661,9 +3106,19 @@ instance.web.form.FieldMany2One = instance.web.form.AbstractField.extend(_.exten return ! this.get("value"); }, focus: function () { - this.delay_focus(this.$input); - } -})); + this.$input.focus(); + }, + _quick_create: function() { + this.no_ed = true; + this.ed_def.reject(); + return instance.web.form.CompletionFieldMixin._quick_create.apply(this, arguments); + }, + _search_create_popup: function() { + this.no_ed = true; + this.ed_def.reject(); + return instance.web.form.CompletionFieldMixin._search_create_popup.apply(this, arguments); + }, +}); /* # Values: (0, 0, { fields }) create @@ -2724,6 +3179,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ }, start: function() { this._super.apply(this, arguments); + this.$el.addClass('oe_form_field oe_form_field_one2many'); var self = this; @@ -2731,7 +3187,6 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ this.dataset.o2m = this; this.dataset.parent_view = this.view; this.dataset.child_name = this.name; - //this.dataset.child_name = this.dataset.on_change.add_last(function() { self.trigger_on_change(); }); @@ -2758,11 +3213,18 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ }, load_views: function() { var self = this; - + var modes = this.node.attrs.mode; modes = !!modes ? modes.split(",") : ["tree"]; var views = []; _.each(modes, function(mode) { + if (! _.include(["list", "tree", "graph", "kanban"], mode)) { + try { + throw new Error(_.str.sprintf("View type '%s' is not supported in One2Many.", mode)); + } catch(e) { + instance.webclient.crashmanager.on_javascript_exception(e) + } + } var view = { view_id: false, view_type: mode == "tree" ? "list" : mode, @@ -2772,23 +3234,37 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ view.embedded_view = self.field.views[mode]; } if(view.view_type === "list") { - view.options.selectable = self.multi_selection; + _.extend(view.options, { + addable: null, + selectable: self.multi_selection, + sortable: false, + import_enabled: false, + deletable: true + }); if (self.get("effective_readonly")) { - view.options.addable = null; - view.options.deletable = null; + _.extend(view.options, { + deletable: null, + reorderable: false, + }); } } else if (view.view_type === "form") { if (self.get("effective_readonly")) { view.view_type = 'form'; } - view.options.not_interactible_on_create = true; + _.extend(view.options, { + not_interactible_on_create: true, + }); } else if (view.view_type === "kanban") { - view.options.confirm_on_delete = false; + _.extend(view.options, { + confirm_on_delete: false, + }); if (self.get("effective_readonly")) { - view.options.action_buttons = false; - view.options.quick_creatable = false; - view.options.creatable = false; - view.options.read_only_mode = true; + _.extend(view.options, { + action_buttons: false, + quick_creatable: false, + creatable: false, + read_only_mode: true, + }); } } views.push(view); @@ -2806,11 +3282,14 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ this.viewmanager.on_controller_inited.add_last(function(view_type, controller) { controller.o2m = self; if (view_type == "list") { - if (self.get("effective_readonly")) - controller.set_editable(false); + if (self.get("effective_readonly")) { + controller.on('edit:before', self, function (e) { + e.cancel = true; + }); + } } else if (view_type === "form") { if (self.get("effective_readonly")) { - $(".oe_form_buttons", controller.$element).children().remove(); + $(".oe_form_buttons", controller.$el).children().remove(); } controller.on_record_loaded.add_last(function() { once.resolve(); @@ -2831,7 +3310,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ }); this.is_setted.then(function() { $.async_when().then(function () { - self.viewmanager.appendTo(self.$element); + self.viewmanager.appendTo(self.$el); }); }); return def; @@ -2913,6 +3392,7 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ this.dataset.index = 0; } self.is_setted.resolve(); + this.trigger_on_change(); return self.reload_current_view(); }, get_value: function() { @@ -2939,42 +3419,44 @@ instance.web.form.FieldOne2Many = instance.web.form.AbstractField.extend({ save_any_view: function() { if (this.doing_on_change) return false; - return this.session.synchronized_mode(_.bind(function() { - if (this.viewmanager && this.viewmanager.views && this.viewmanager.active_view && - this.viewmanager.views[this.viewmanager.active_view] && - this.viewmanager.views[this.viewmanager.active_view].controller) { - var view = this.viewmanager.views[this.viewmanager.active_view].controller; - if (this.viewmanager.active_view === "form") { - if (!view.is_initialized.isResolved()) { - return false; - } - var res = $.when(view.do_save()); - if (!res.isResolved() && !res.isRejected()) { - console.warn("Asynchronous get_value() is not supported in form view."); - } - return res; - } else if (this.viewmanager.active_view === "list") { - var res = $.when(view.ensure_saved()); - if (!res.isResolved() && !res.isRejected()) { - console.warn("Asynchronous get_value() is not supported in list view."); - } - return res; - } - } - return false; - }, this)); + return this.session.synchronized_mode(_.bind(function() { + if (this.viewmanager && this.viewmanager.views && this.viewmanager.active_view && + this.viewmanager.views[this.viewmanager.active_view] && + this.viewmanager.views[this.viewmanager.active_view].controller) { + var view = this.viewmanager.views[this.viewmanager.active_view].controller; + if (this.viewmanager.active_view === "form") { + if (!view.is_initialized.isResolved()) { + return false; + } + var res = $.when(view.do_save()); + if (!res.isResolved() && !res.isRejected()) { + console.warn("Asynchronous get_value() is not supported in form view."); + } + return res; + } else if (this.viewmanager.active_view === "list") { + var res = $.when(view.ensure_saved()); + if (!res.isResolved() && !res.isRejected()) { + console.warn("Asynchronous get_value() is not supported in list view."); + } + return res; + } + } + return false; + }, this)); }, is_syntax_valid: function() { - if (!this.viewmanager.views[this.viewmanager.active_view]) + if (! this.viewmanager || ! this.viewmanager.views[this.viewmanager.active_view]) return true; var view = this.viewmanager.views[this.viewmanager.active_view].controller; - if (this.viewmanager.active_view === "form") { - for (var f in view.fields) { - f = view.fields[f]; - if (!f.is_valid()) { - return false; - } - } + switch (this.viewmanager.active_view) { + case 'form': + return _(view.fields).chain() + .invoke('is_valid') + .all(_.identity) + .value(); + break; + case 'list': + return view.is_valid(); } return true; }, @@ -2989,6 +3471,7 @@ instance.web.form.One2ManyViewManager = instance.web.ViewManager.extend({ form: 'instance.web.form.One2ManyFormView', kanban: 'instance.web.form.One2ManyKanbanView', }); + this.__ignore_blur = false; }, switch_view: function(mode, unused) { if (mode !== 'form') { @@ -2998,7 +3481,7 @@ instance.web.form.One2ManyViewManager = instance.web.ViewManager.extend({ var id = self.o2m.dataset.index !== null ? self.o2m.dataset.ids[self.o2m.dataset.index] : null; var pop = new instance.web.form.FormOpenPopup(self.o2m.view); pop.show_element(self.o2m.field.relation, id, self.o2m.build_context(), { - title: _t("Open: ") + self.name, + title: _t("Open: ") + self.o2m.string, create_function: function(data) { return self.o2m.dataset.create(data).then(function(r) { self.o2m.dataset.set_ids(self.o2m.dataset.ids.concat([r.result])); @@ -3034,8 +3517,51 @@ instance.web.form.One2ManyDataSet = instance.web.BufferedDataSet.extend({ instance.web.form.One2ManyListView = instance.web.ListView.extend({ _template: 'One2Many.listview', + init: function (parent, dataset, view_id, options) { + this._super(parent, dataset, view_id, _.extend(options || {}, { + ListType: instance.web.form.One2ManyList + })); + this.on('edit:before', this, this.proxy('_before_edit')); + this.on('edit:after', this, this.proxy('_after_edit')); + this.on('save:before cancel:before', this, this.proxy('_before_unedit')); + + this.records + .bind('add', this.proxy("changed_records")) + .bind('edit', this.proxy("changed_records")) + .bind('remove', this.proxy("changed_records")); + }, + start: function () { + var ret = this._super(); + this.$el + .off('mousedown.handleButtons') + .on('mousedown.handleButtons', 'table button', this.proxy('_button_down')); + return ret; + }, + changed_records: function () { + this.o2m.trigger_on_change(); + }, + is_valid: function () { + var form = this.editor.form; + + // If the form has not been modified, the view can only be valid + // NB: is_dirty will also be set on defaults/onchanges/whatever? + // oe_form_dirty seems to only be set on actual user actions + if (!form.$el.is('.oe_form_dirty')) { + return true; + } + this.o2m._dirty_flag = true; + + // Otherwise validate internal form + return _(form.fields).chain() + .invoke(function () { + this._check_css_flags(); + return this.is_valid(); + }) + .all(_.identity) + .value(); + }, do_add_record: function () { - if (this.options.editable) { + if (this.editable()) { this._super.apply(this, arguments); } else { var self = this; @@ -3043,7 +3569,7 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ pop.select_element( self.o2m.field.relation, { - title: _t("Create: ") + self.name, + title: _t("Create: ") + self.o2m.string, initial_view: "form", alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined, create_function: function(data, callback, error_callback) { @@ -3071,7 +3597,7 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ var self = this; var pop = new instance.web.form.FormOpenPopup(self.o2m.view); pop.show_element(self.o2m.field.relation, id, self.o2m.build_context(), { - title: _t("Open: ") + self.name, + title: _t("Open: ") + self.o2m.string, write_function: function(id, data) { return self.o2m.dataset.write(id, data, {}, function(r) { self.o2m.reload_current_view(); @@ -3084,13 +3610,147 @@ instance.web.form.One2ManyListView = instance.web.ListView.extend({ return self.o2m.dataset.read_ids.apply(self.o2m.dataset, arguments); }, form_view_options: {'not_interactible_on_create':true}, - readonly: self.o2m.get("effective_readonly") + readonly: !this.is_action_enabled('edit') || self.o2m.get("effective_readonly") }); }, do_button_action: function (name, id, callback) { + if (!_.isNumber(id)) { + instance.webclient.notification.warn( + _t("Action Button"), + _t("The o2m record must be saved before an action can be used")); + return; + } + var parent_form = this.o2m.view; + var self = this; + this.ensure_saved().pipe(function () { + return parent_form.do_save(); + }).then(function () { + self.handle_button(name, id, callback); + }); + }, + + _before_edit: function () { + this.__ignore_blur = false; + this.editor.form.on('blurred', this, this._on_form_blur); + }, + _after_edit: function () { + // The form's blur thing may be jiggered during the edition setup, + // potentially leading to the o2m instasaving the row. Cancel any + // blurring triggered the edition startup here + this.editor.form.widgetFocused(); + }, + _before_unedit: function () { + this.editor.form.off('blurred', this, this._on_form_blur); + }, + _button_down: function () { + // If a button is clicked (usually some sort of action button), it's + // the button's responsibility to ensure the editable list is in the + // correct state -> ignore form blurring + this.__ignore_blur = true; + }, + /** + * Handles blurring of the nested form (saves the currently edited row), + * unless the flag to ignore the event is set to ``true`` + * + * Makes the internal form go away + */ + _on_form_blur: function () { + if (this.__ignore_blur) { + this.__ignore_blur = false; + return; + } + // FIXME: why isn't there an API for this? + if (this.editor.form.$el.hasClass('oe_form_dirty')) { + this.save_edition(); + return; + } + this.cancel_edition(); + }, + keyup_ENTER: function () { + // blurring caused by hitting the [Return] key, should skip the + // autosave-on-blur and let the handler for [Return] do its thing (save + // the current row *anyway*, then create a new one/edit the next one) + this.__ignore_blur = true; + this._super.apply(this, arguments); + }, + do_delete: function (ids) { + var self = this; + var next = $.when(); + var _super = this._super; + // handle deletion of an item which does not exist + // TODO: better handle that in the editable list? + var false_id_index = _(ids).indexOf(false); + if (false_id_index !== -1) { + ids.splice(false_id_index, 1); + next = this.cancel_edition(true); + } + return next.pipe(function () { + // wheeee + var confirm = window.confirm; + window.confirm = function () { return true; }; + try { + return _super.call(self, ids); + } finally { + window.confirm = confirm; + } + }); + } +}); +instance.web.form.One2ManyList = instance.web.ListView.List.extend({ + pad_table_to: function (count) { + if (!this.view.is_action_enabled('create')) { + this._super(count); + } else { + this._super(count > 0 ? count - 1 : 0); + } + + // magical invocation of wtf does that do + if (this.view.o2m.get('effective_readonly')) { + return; + } + var self = this; - var def = $.Deferred().then(callback).then(function() {self.o2m.view.reload();}); - return this._super(name, id, _.bind(def.resolve, def)); + var columns = _(this.columns).filter(function (column) { + return column.invisible !== '1'; + }).length; + if (this.options.selectable) { columns++; } + if (this.options.deletable) { columns++; } + + if (!this.view.is_action_enabled('create')) { + return; + } + + var $cell = $('<td>', { + colspan: columns, + 'class': 'oe_form_field_one2many_list_row_add' + }).append( + $('<a>', {href: '#'}).text(_t("Add a row")) + .mousedown(function () { + // FIXME: needs to be an official API somehow + if (self.view.editor.is_editing()) { + self.view.__ignore_blur = true; + } + }) + .click(function (e) { + e.preventDefault(); + e.stopPropagation(); + // FIXME: there should also be an API for that one + if (self.view.editor.form.__blur_timeout) { + clearTimeout(self.view.editor.form.__blur_timeout); + self.view.editor.form.__blur_timeout = false; + } + self.view.ensure_saved().then(function () { + self.view.do_add_record(); + }); + })); + + var $padding = this.$current.find('tr:not([data-id]):first'); + var $newrow = $('<tr>').append($cell); + if ($padding.length) { + $padding.before($newrow); + } else { + this.$current.append($newrow) + } } }); @@ -3119,8 +3779,7 @@ instance.web.form.One2ManyKanbanView = instance.web_kanban.KanbanView.extend({ }); } -instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.CompletionFieldMixin, - instance.web.form.ReinitializeFieldMixin, { +instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(instance.web.form.CompletionFieldMixin, instance.web.form.ReinitializeFieldMixin, { template: "FieldMany2ManyTags", init: function() { this._super.apply(this, arguments); @@ -3138,7 +3797,7 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(_. if (this.get("effective_readonly")) return; var self = this; - self.$text = $("textarea", this.$element); + self.$text = $("textarea", this.$el); self.$text.textext({ plugins : 'tags arrow autocomplete', autocomplete: { @@ -3196,10 +3855,10 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(_. self._drop_shown = true; }); self.tags = self.$text.textext()[0].tags(); - $("textarea", this.$element).focusout(function() { + $("textarea", this.$el).focusout(function() { self.$text.trigger("setInputData", ""); }).keydown(function(e) { - if (event.keyCode === 9 && self._drop_shown) { + if (e.which === $.ui.keyCode.TAB && self._drop_shown) { self.$text.textext()[0].autocomplete().selectFromDropdown(); } }); @@ -3221,22 +3880,23 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(_. render_value: function() { var self = this; var dataset = new instance.web.DataSetStatic(this, this.field.relation, self.view.dataset.get_context()); + var values = self.get("value") var handle_names = function(data) { var indexed = {}; _.each(data, function(el) { indexed[el[0]] = el; }); - data = _.map(self.get("value"), function(el) { return indexed[el]; }); + data = _.map(values, function(el) { return indexed[el]; }); if (! self.get("effective_readonly")) { self.tags.containerElement().children().remove(); - $("textarea", self.$element).css("padding-left", "3px"); + $("textarea", self.$el).css("padding-left", "3px"); self.tags.addTags(_.map(data, function(el) {return {name: el[1], id:el[0]};})); } else { - self.$element.html(QWeb.render("FieldMany2ManyTags.box", {elements: data})); + self.$el.html(QWeb.render("FieldMany2ManyTag", {elements: data})); } }; - if (! self.get('values') || self.get('values').length > 0) { - this._display_orderer.add(dataset.name_get(self.get("value"))).then(handle_names); + if (! values || values.length > 0) { + this._display_orderer.add(dataset.name_get(values)).then(handle_names); } else { handle_names([]); } @@ -3244,7 +3904,7 @@ instance.web.form.FieldMany2ManyTags = instance.web.form.AbstractField.extend(_. add_id: function(id) { this.set({'value': _.uniq(this.get('value').concat([id]))}); }, -})); +}); /* * TODO niv: clean those deferred stuff, it could be better @@ -3260,6 +3920,7 @@ instance.web.form.FieldMany2Many = instance.web.form.AbstractField.extend({ }, start: function() { this._super.apply(this, arguments); + this.$el.addClass('oe_form_field oe_form_field_many2many'); var self = this; @@ -3268,7 +3929,7 @@ instance.web.form.FieldMany2Many = instance.web.form.AbstractField.extend({ this.dataset.on_unlink.add_last(function(ids) { self.dataset_changed(); }); - + this.is_setted.then(function() { self.load_view(); }); @@ -3281,7 +3942,7 @@ instance.web.form.FieldMany2Many = instance.web.form.AbstractField.extend({ }); }); }); - }) + }); }, set_value: function(value_) { value_ = value_ || []; @@ -3297,12 +3958,19 @@ instance.web.form.FieldMany2Many = instance.web.form.AbstractField.extend({ get_value: function() { return [commands.replace_with(this.get('value'))]; }, + + is_false: function () { + return _(this.dataset.ids).isEmpty(); + }, load_view: function() { var self = this; this.list_view = new instance.web.form.Many2ManyListView(this, this.dataset, false, { 'addable': self.get("effective_readonly") ? null : _t("Add"), 'deletable': self.get("effective_readonly") ? false : true, 'selectable': self.multi_selection, + 'sortable': false, + 'reorderable': false, + 'import_enabled': false, }); var embedded = (this.field.views || {}).tree; if (embedded) { @@ -3315,7 +3983,7 @@ instance.web.form.FieldMany2Many = instance.web.form.AbstractField.extend({ loaded.resolve(); }); $.async_when().then(function () { - self.list_view.appendTo(self.$element); + self.list_view.appendTo(self.$el); }); return loaded; }, @@ -3347,7 +4015,7 @@ instance.web.form.Many2ManyListView = instance.web.ListView.extend(/** @lends in pop.select_element( this.model, { - title: _t("Add: ") + this.name + title: _t("Add: ") + this.m2m_field.string }, new instance.web.CompoundDomain(this.m2m_field.build_domain(), ["!", ["id", "in", this.m2m_field.dataset.ids]]), this.m2m_field.build_context() @@ -3367,7 +4035,7 @@ instance.web.form.Many2ManyListView = instance.web.ListView.extend(/** @lends in var self = this; var pop = new instance.web.form.FormOpenPopup(this); pop.show_element(this.dataset.model, id, this.m2m_field.build_context(), { - title: _t("Open: ") + this.name, + title: _t("Open: ") + this.m2m_field.string, readonly: this.getParent().get("effective_readonly") }); pop.on_write_completed.add_last(function() { @@ -3376,7 +4044,7 @@ instance.web.form.Many2ManyListView = instance.web.ListView.extend(/** @lends in } }); -instance.web.form.FieldMany2ManyKanban = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.CompletionFieldMixin, { +instance.web.form.FieldMany2ManyKanban = instance.web.form.AbstractField.extend(instance.web.form.CompletionFieldMixin, { disable_utility_classes: true, init: function(field_manager, node) { this._super(field_manager, node); @@ -3396,7 +4064,7 @@ instance.web.form.FieldMany2ManyKanban = instance.web.form.AbstractField.extend( this.dataset.on_unlink.add_last(function(ids) { self.dataset_changed(); }); - + this.is_setted.then(function() { self.load_view(); }); @@ -3443,7 +4111,7 @@ instance.web.form.FieldMany2ManyKanban = instance.web.form.AbstractField.extend( }); this.kanban_view.do_switch_view.add_last(_.bind(this.open_popup, this)); $.async_when().then(function () { - self.kanban_view.appendTo(self.$element); + self.kanban_view.appendTo(self.$el); }); return loaded; }, @@ -3465,7 +4133,7 @@ instance.web.form.FieldMany2ManyKanban = instance.web.form.AbstractField.extend( pop.select_element( this.field.relation, { - title: _t("Add: ") + this.name + title: _t("Add: ") + this.string }, new instance.web.CompoundDomain(this.build_domain(), ["!", ["id", "in", this.dataset.ids]]), this.build_context() @@ -3483,7 +4151,7 @@ instance.web.form.FieldMany2ManyKanban = instance.web.form.AbstractField.extend( var id = self.dataset.ids[self.dataset.index]; var pop = new instance.web.form.FormOpenPopup(self.view); pop.show_element(self.field.relation, id, self.build_context(), { - title: _t("Open: ") + self.name, + title: _t("Open: ") + self.string, write_function: function(id, data, options) { return self.dataset.write(id, data, {}).then(function() { self.reload_content(); @@ -3499,7 +4167,7 @@ instance.web.form.FieldMany2ManyKanban = instance.web.form.AbstractField.extend( add_id: function(id) { this.quick_create.add_id(id); }, -})); +}); function m2m_kanban_lazy_init() { if (instance.web.form.Many2ManyKanbanView) @@ -3512,7 +4180,7 @@ instance.web.form.Many2ManyKanbanView = instance.web_kanban.KanbanView.extend({ }); instance.web.form.Many2ManyQuickCreate = instance.web.Widget.extend({ template: 'Many2ManyKanban.quick_create', - + /** * close_btn: If true, the widget will display a "Close" button able to trigger * a "close" event. @@ -3527,7 +4195,7 @@ instance.web.form.Many2ManyQuickCreate = instance.web.Widget.extend({ }, start: function () { var self = this; - self.$text = this.$element.find('input').css("width", "200px"); + self.$text = this.$el.find('input').css("width", "200px"); self.$text.textext({ plugins : 'arrow autocomplete', autocomplete: { @@ -3584,7 +4252,7 @@ instance.web.form.Many2ManyQuickCreate = instance.web.Widget.extend({ /** * Class with everything which is common between FormOpenPopup and SelectCreatePopup. */ -instance.web.form.AbstractFormPopup = instance.web.OldWidget.extend({ +instance.web.form.AbstractFormPopup = instance.web.Widget.extend({ template: "AbstractFormPopup.render", /** * options: @@ -3627,14 +4295,16 @@ instance.web.form.AbstractFormPopup = instance.web.OldWidget.extend({ display_popup: function() { var self = this; this.renderElement(); - new instance.web.Dialog(this, { - width: '90%', + var dialog = new instance.web.Dialog(this, { min_width: '800px', + dialogClass: 'oe_act_window', close: function() { self.check_exit(true); }, title: this.options.title || "", - }, this.$element).open(); + buttons: [{text:"tmp"}], + }, this.$el).open(); + this.$buttonpane = dialog.$el.dialog("widget").find(".ui-dialog-buttonpane").html(""); this.start(); }, on_write_completed: function() {}, @@ -3650,16 +4320,21 @@ instance.web.form.AbstractFormPopup = instance.web.OldWidget.extend({ if (this.row_id !== null) { options.initial_mode = this.options.readonly ? "view" : "edit"; } + _.extend(options, { + $buttons: this.$buttonpane, + }); this.view_form = new instance.web.FormView(this, this.dataset, false, options); if (this.options.alternative_form_view) { this.view_form.set_embedded_view(this.options.alternative_form_view); } - this.view_form.appendTo(this.$element.find(".oe-form-view-popup-form-placeholder")); + this.view_form.appendTo(this.$el.find(".oe_popup_form")); this.view_form.on_loaded.add_last(function() { - var $buttons = self.view_form.$element.find(".oe_form_buttons"); var multi_select = self.row_id === null && ! self.options.disable_multiple_selection; - $buttons.html(QWeb.render("AbstractFormPopup.buttons", {multi_select: multi_select})); - var $snbutton = $buttons.find(".oe_abstractformpopup-form-save-new"); + self.$buttonpane.html(QWeb.render("AbstractFormPopup.buttons", { + multi_select: multi_select, + readonly: self.row_id !== null && self.options.readonly, + })); + var $snbutton = self.$buttonpane.find(".oe_abstractformpopup-form-save-new"); $snbutton.click(function() { $.when(self.view_form.do_save()).then(function() { self.view_form.reload_mutex.exec(function() { @@ -3667,7 +4342,7 @@ instance.web.form.AbstractFormPopup = instance.web.OldWidget.extend({ }); }); }); - var $sbutton = $buttons.find(".oe_abstractformpopup-form-save"); + var $sbutton = self.$buttonpane.find(".oe_abstractformpopup-form-save"); $sbutton.click(function() { $.when(self.view_form.do_save()).then(function() { self.view_form.reload_mutex.exec(function() { @@ -3675,15 +4350,10 @@ instance.web.form.AbstractFormPopup = instance.web.OldWidget.extend({ }); }); }); - var $cbutton = $buttons.find(".oe_abstractformpopup-form-close"); + var $cbutton = self.$buttonpane.find(".oe_abstractformpopup-form-close"); $cbutton.click(function() { self.check_exit(); }); - if (self.row_id !== null && self.options.readonly) { - $snbutton.hide(); - $sbutton.hide(); - $cbutton.text(_t("Close")); - } self.view_form.do_show(); }); }, @@ -3697,7 +4367,7 @@ instance.web.form.AbstractFormPopup = instance.web.OldWidget.extend({ this.destroy(); }, destroy: function () { - this.$element.dialog('close'); + this.$el.dialog('close'); this._super(); }, }); @@ -3782,32 +4452,32 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend self.dataset, false, _.extend({'deletable': false, 'selectable': !self.options.disable_multiple_selection, - 'read_only': true, + 'import_enabled': false, + '$buttons': self.$buttonpane, }, self.options.list_view_options || {})); + self.view_list.on('edit:before', self, function (e) { + e.cancel = true; + }); self.view_list.popup = self; - self.view_list.appendTo($(".oe-select-create-popup-view-list", self.$element)).pipe(function() { + self.view_list.appendTo($(".oe_popup_list", self.$el)).pipe(function() { self.view_list.do_show(); }).pipe(function() { self.searchview.do_search(); }); self.view_list.on_loaded.add_last(function() { - var $buttons = self.view_list.$element.find(".oe-actions"); - $buttons.prepend(QWeb.render("SelectCreatePopup.search.buttons")); - var $cbutton = $buttons.find(".oe_selectcreatepopup-search-close"); + self.$buttonpane.html(QWeb.render("SelectCreatePopup.search.buttons", {widget:self})); + var $cbutton = self.$buttonpane.find(".oe_selectcreatepopup-search-close"); $cbutton.click(function() { self.destroy(); }); - var $sbutton = $buttons.find(".oe_selectcreatepopup-search-select"); - if(self.options.disable_multiple_selection) { - $sbutton.hide(); - } + var $sbutton = self.$buttonpane.find(".oe_selectcreatepopup-search-select"); $sbutton.click(function() { self.on_select_elements(self.selected_ids); self.destroy(); }); }); }); - this.searchview.appendTo($(".oe-select-create-popup-view-list", self.$element)); + this.searchview.appendTo($(".oe_popup_list", self.$el)); }, do_search: function(domains, contexts, groupbys) { var self = this; @@ -3820,11 +4490,12 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend }); }, on_click_element: function(ids) { + var self = this; this.selected_ids = ids || []; if(this.selected_ids.length > 0) { - this.$element.find(".oe_selectcreatepopup-search-select").removeAttr('disabled'); + self.$buttonpane.find(".oe_selectcreatepopup-search-select").removeAttr('disabled'); } else { - this.$element.find(".oe_selectcreatepopup-search-select").attr('disabled', "disabled"); + self.$buttonpane.find(".oe_selectcreatepopup-search-select").attr('disabled', "disabled"); } }, new_object: function() { @@ -3832,7 +4503,7 @@ instance.web.form.SelectCreatePopup = instance.web.form.AbstractFormPopup.extend this.searchview.hide(); } if (this.view_list) { - this.view_list.$element.hide(); + this.view_list.$el.hide(); } this.setup_form_view(); }, @@ -3852,7 +4523,7 @@ instance.web.form.SelectCreateListView = instance.web.ListView.extend({ } }); -instance.web.form.FieldReference = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, { +instance.web.form.FieldReference = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, { template: 'FieldReference', init: function(field_manager, node) { this._super(field_manager, node); @@ -3865,7 +4536,7 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(_.exte var sel = this.selection.get_value(); this.m2o.field.relation = sel; this.m2o.set_value(false); - this.m2o.$element.toggle(sel !== false); + this.m2o.$el.toggle(sel !== false); } }, destroy_content: function() { @@ -3879,25 +4550,32 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(_.exte } }, initialize_content: function() { + var self = this; this.selection = new instance.web.form.FieldSelection(this, { attrs: { - name: 'selection' + name: 'selection', + modifiers: JSON.stringify({readonly: this.get('effective_readonly')}), }}); this.selection.view = this.view; - this.selection.set({force_readonly: this.get('effective_readonly')}); this.selection.on("change:value", this, this.on_selection_changed); - this.selection.$element = $(".oe_form_view_reference_selection", this.$element); + this.selection.setElement(this.$(".oe_form_view_reference_selection")); this.selection.renderElement(); this.selection.start(); + this.selection + .on('focused', null, function () {self.trigger('focused')}) + .on('blurred', null, function () {self.trigger('blurred')}); this.m2o = new instance.web.form.FieldMany2One(this, { attrs: { - name: 'm2o' + name: 'm2o', + modifiers: JSON.stringify({readonly: this.get('effective_readonly')}), }}); this.m2o.view = this.view; - this.m2o.set({force_readonly: this.get("effective_readonly")}); this.m2o.on("change:value", this, this.data_changed); - this.m2o.$element = $(".oe_form_view_reference_m2o", this.$element); + this.m2o.setElement(this.$(".oe_form_view_reference_m2o")); this.m2o.renderElement(); this.m2o.start(); + this.m2o + .on('focused', null, function () {self.trigger('focused')}) + .on('blurred', null, function () {self.trigger('blurred')}); }, is_false: function() { return typeof(this.get_value()) !== 'string'; @@ -3944,18 +4622,27 @@ instance.web.form.FieldReference = instance.web.form.AbstractField.extend(_.exte } throw Exception("Should not happen"); }, -})); +}); -instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(_.extend({}, instance.web.form.ReinitializeFieldMixin, { +instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeFieldMixin, { init: function(field_manager, node) { + var self = this; this._super(field_manager, node); - this.iframe = this.element_id + '_iframe'; this.binary_value = false; + this.fileupload_id = _.uniqueId('oe_fileupload'); + $(window).on(this.fileupload_id, function() { + var args = [].slice.call(arguments).slice(1); + self.on_file_uploaded.apply(self, args); + }); + }, + stop: function() { + $(window).off(this.fileupload_id); + this._super.apply(this, arguments); }, initialize_content: function() { - this.$element.find('input.oe-binary-file').change(this.on_file_change); - this.$element.find('button.oe-binary-file-save').click(this.on_save_as); - this.$element.find('.oe-binary-file-clear').click(this.on_clear); + this.$el.find('input.oe_form_binary_file').change(this.on_file_change); + this.$el.find('button.oe_form_binary_file_save').click(this.on_save_as); + this.$el.find('.oe_form_binary_file_clear').click(this.on_clear); }, human_filesize : function(size) { var units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; @@ -3970,42 +4657,62 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(_.extend( // TODO: on modern browsers, we could directly read the file locally on client ready to be used on image cropper // http://www.html5rocks.com/tutorials/file/dndfiles/ // http://deepliquid.com/projects/Jcrop/demos.php?demo=handler - window[this.iframe] = this.on_file_uploaded; - if ($(e.target).val() != '') { - this.$element.find('form.oe-binary-form input[name=session_id]').val(this.session.session_id); - this.$element.find('form.oe-binary-form').submit(); - this.$element.find('.oe-binary-progress').show(); - this.$element.find('.oe-binary').hide(); + + if ($(e.target).val() !== '') { + this.$el.find('form.oe_form_binary_form input[name=session_id]').val(this.session.session_id); + this.$el.find('form.oe_form_binary_form').submit(); + this.$el.find('.oe_form_binary_progress').show(); + this.$el.find('.oe_form_binary').hide(); } }, on_file_uploaded: function(size, name, content_type, file_base64) { - delete(window[this.iframe]); if (size === false) { this.do_warn("File Upload", "There was a problem while uploading your file"); // TODO: use openerp web crashmanager console.warn("Error while uploading file : ", name); } else { + this.filename = name; this.on_file_uploaded_and_valid.apply(this, arguments); } - this.$element.find('.oe-binary-progress').hide(); - this.$element.find('.oe-binary').show(); + this.$el.find('.oe_form_binary_progress').hide(); + this.$el.find('.oe_form_binary').show(); }, on_file_uploaded_and_valid: function(size, name, content_type, file_base64) { }, - on_save_as: function() { - $.blockUI(); - this.session.get_file({ - url: '/web/binary/saveas_ajax', - data: {data: JSON.stringify({ - model: this.view.dataset.model, - id: (this.view.datarecord.id || ''), - field: this.name, - filename_field: (this.node.attrs.filename || ''), - context: this.view.dataset.get_context() - })}, - complete: $.unblockUI, - error: instance.webclient.crashmanager.on_rpc_error - }); + on_save_as: function(ev) { + var value = this.get('value'); + if (!value) { + this.do_warn(_t("Save As..."), _t("The field is empty, there's nothing to save !")); + ev.stopPropagation(); + } else if (this._dirty_flag) { + var link = this.$('.oe_form_binary_file_save_data')[0]; + link.download = this.filename || "download.bin"; // Works on only on Google Chrome + //link.target = '_blank'; + link.href = "data:application/octet-stream;base64," + value; + } else { + instance.web.blockUI(); + this.session.get_file({ + url: '/web/binary/saveas_ajax', + data: {data: JSON.stringify({ + model: this.view.dataset.model, + id: (this.view.datarecord.id || ''), + field: this.name, + filename_field: (this.node.attrs.filename || ''), + context: this.view.dataset.get_context() + })}, + complete: instance.web.unblockUI, + error: instance.webclient.crashmanager.on_rpc_error + }); + ev.stopPropagation(); + return false; + } + }, + set_filename: function(value) { + var filename = this.node.attrs.filename; + if (this.view.fields[filename]) { + this.view.fields[filename].set_value(value); + this.view.fields[filename].on_ui_change(); + } }, on_clear: function() { if (this.get('value') !== false) { @@ -4014,7 +4721,7 @@ instance.web.form.FieldBinary = instance.web.form.AbstractField.extend(_.extend( } return false; } -})); +}); instance.web.form.FieldBinaryFile = instance.web.form.FieldBinary.extend({ template: 'FieldBinaryFile', @@ -4022,7 +4729,7 @@ instance.web.form.FieldBinaryFile = instance.web.form.FieldBinary.extend({ this._super(); if (this.get("effective_readonly")) { var self = this; - this.$element.find('a').click(function() { + this.$el.find('a').click(function() { if (self.get('value')) { self.on_save_as(); } @@ -4042,12 +4749,12 @@ instance.web.form.FieldBinaryFile = instance.web.form.FieldBinary.extend({ } else { show_value = (this.get('value') != null && this.get('value') !== false) ? this.get('value') : ''; } - this.$element.find('input').eq(0).val(show_value); + this.$el.find('input').eq(0).val(show_value); } else { - this.$element.find('a').show(!!this.get('value')); + this.$el.find('a').show(!!this.get('value')); if (this.get('value')) { var show_value = _t("Download") + " " + (this.view.datarecord[this.node.attrs.filename] || ''); - this.$element.find('a').text(show_value); + this.$el.find('a').text(show_value); } } }, @@ -4055,7 +4762,7 @@ instance.web.form.FieldBinaryFile = instance.web.form.FieldBinary.extend({ this.binary_value = true; this.set({'value': file_base64}); var show_value = name + " (" + this.human_filesize(size) + ")"; - this.$element.find('input').eq(0).val(show_value); + this.$el.find('input').eq(0).val(show_value); this.set_filename(name); }, set_filename: function(value_) { @@ -4066,38 +4773,43 @@ instance.web.form.FieldBinaryFile = instance.web.form.FieldBinary.extend({ }, on_clear: function() { this._super.apply(this, arguments); - this.$element.find('input').eq(0).val(''); + this.$el.find('input').eq(0).val(''); this.set_filename(''); } }); instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({ template: 'FieldBinaryImage', - initialize_content: function() { - this._super(); - if (!this.get("effective_readonly")) { - this.$element.find('.oe_form_field_image_controls').show(); - } else { - this.$element.find('.oe_form_field_image_controls').hide(); - } - }, set_value: function(value_) { this._super.apply(this, arguments); this.render_value(); }, render_value: function() { + var self = this; var url; - if (this.get('value') && this.get('value').substr(0, 10).indexOf(' ') == -1) { + if (this.get('value') && ! /^\d+(\.\d*)? \w+$/.test(this.get('value'))) { url = 'data:image/png;base64,' + this.get('value'); } else if (this.get('value')) { + var id = escape(JSON.stringify(this.view.datarecord.id || null)); + var field = this.name; + if (this.options.preview_image) + field = this.options.preview_image; url = '/web/binary/image?session_id=' + this.session.session_id + '&model=' + - this.view.dataset.model +'&id=' + (this.view.datarecord.id || '') + '&field=' + this.name + '&t=' + (new Date().getTime()); + this.view.dataset.model +'&id=' + id + '&field=' + field + '&t=' + (new Date().getTime()); } else { url = "/web/static/src/img/placeholder.png"; } - var img = QWeb.render("FieldBinaryImage-img", { widget: this, url: url }); - this.$element.find('> img').remove(); - this.$element.prepend(img); + var $img = $(QWeb.render("FieldBinaryImage-img", { widget: this, url: url })); + this.$el.find('> img').remove(); + this.$el.prepend($img); + $img.load(function() { + if (! self.options.size) + return; + $img.css("max-width", "" + self.options.size[0] + "px"); + $img.css("max-height", "" + self.options.size[1] + "px"); + $img.css("margin-left", "" + (self.options.size[0] - $img.width()) / 2 + "px"); + $img.css("margin-top", "" + (self.options.size[1] - $img.height()) / 2 + "px"); + }); }, on_file_change: function() { this.render_value(); @@ -4115,13 +4827,23 @@ instance.web.form.FieldBinaryImage = instance.web.form.FieldBinary.extend({ }); instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({ - tagName: "span", + template: "FieldStatus", + init: function(field_manager, node) { + this._super(field_manager, node); + this.options.clickable = this.options.clickable || (this.node.attrs || {}).clickable || false; + this.options.visible = this.options.visible || (this.node.attrs || {}).statusbar_visible || false; + this.selected_value = null; + }, start: function() { this._super(); - this.selected_value = null; - // preview in start only for selection fields, because of the dynamic behavior of many2one fields. - if (this.field.type in ['selection']) { - this.render_list(); + // backward compatibility + this.loaded = new $.Deferred(); + if (this.options.clickable) { + this.$el.on('click','li',this.on_click_stage); + } + // TODO move the following into css :after + if (this.$el.parent().is('header')) { + this.$el.after('<div class="oe_clear"/>'); } }, set_value: function(value_) { @@ -4137,97 +4859,73 @@ instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({ } // trick to be sure all values are loaded in the form, therefore // enabling the evaluation of dynamic domains + self.selection = []; $.async_when().then(function() { - return self.render_list(); + self.get_selection(); }); + return this.loaded; }, - - /** Get the status list and render them - * to_show: [[identifier, value_to_display]] where - * - identifier = key for a selection, id for a many2one - * - display_val = label that will be displayed - * - ex: [[0, "New"]] (many2one) or [["new", "In Progress"]] (selection) - */ - render_list: function() { - var self = this; - // get selection values, filter them and render them - var selection_done = this.get_selection().pipe(self.proxy('filter_selection')).pipe(self.proxy('render_elements')); - }, - - /** Get the selection list to be displayed in the statusbar widget. + /** Get the selection and render it + * selection: [[identifier, value_to_display], ...] * For selection fields: this is directly given by this.field.selection - * For many2one fields : - * - perform a search on the relation of the many2one field (given by - * field.relation ) - * - get the field domain for the search - * - self.build_domain() gives the domain given by the view or by - * the field - * - if the optional statusbar_fold attribute is set to true, make - * an AND with build_domain to hide all 'fold=true' columns - * - make an OR with current value, to be sure it is displayed, - * with the correct order, even if it is folded + * For many2one fields: perform a search on the relation of the many2one field */ get_selection: function() { var self = this; if (this.field.type == "many2one") { - this.selection = []; - // get fold information from widget - var fold = ((this.node.attrs || {}).statusbar_fold || true); - // build final domain: if fold option required, add the - if (fold == true) { - var domain = new instance.web.CompoundDomain(['|'], ['&'], self.build_domain(), [['fold', '=', false]], [['id', '=', self.selected_value]]); - } else { - var domain = new instance.web.CompoundDomain(['|'], self.build_domain(), [['id', '=', self.selected_value]]); + var domain = []; + if(!_.isEmpty(this.field.domain) || !_.isEmpty(this.node.attrs.domain)) { + domain = new instance.web.CompoundDomain(['|'], self.build_domain(), [['id', '=', self.selected_value]]); } - // get a DataSetSearch on the current field relation (ex: crm.lead.stage_id -> crm.case.stage) - var model_ext = new instance.web.DataSetSearch(this, this.field.relation, self.build_context(), domain); - // fetch selection - var read_defer = model_ext.read_slice(['name'], {}).pipe( function (records) { - _(records).each(function (record) { - self.selection.push([record.id, record.name]); - }); + var ds = new instance.web.DataSetSearch(this, this.field.relation, self.build_context(), domain); + ds.read_slice(['name'], {}).done( function (records) { + for(var i = 0; i < records.length; i++) { + self.selection.push([records[i].id, records[i].name]); + } + self.render_elements(); + self.loaded.resolve(); }); } else { - this.selection = this.field.selection; - var read_defer = new $.Deferred().resolve(); - } - return read_defer; - }, - - /** Filters this.selection, according to values coming from the statusbar_visible - * attribute of the field. For example: statusbar_visible="draft,open" - * Currently, the key of (key, label) pairs has to be used in the - * selection of visible items. This feature is not meant to be used - * with many2one fields. - */ - filter_selection: function() { - var self = this; - var shown = _.map(((this.node.attrs || {}).statusbar_visible || "").split(","), - function(x) { return _.str.trim(x); }); - shown = _.select(shown, function(x) { return x.length > 0; }); - - if (shown.length == 0) { - this.to_show = this.selection; - } else { - this.to_show = _.select(this.selection, function(x) { - return _.indexOf(shown, x[0]) !== -1 || x[0] === self.selected_value; - }); + this.loaded.resolve(); + // For field type selection filter values according to + // statusbar_visible attribute of the field. For example: + // statusbar_visible="draft,open". + var selection = this.field.selection; + for(var i=0; i< selection.length; i++) { + var key = selection[i][0]; + if(key == this.selected_value || !this.options.visible || this.options.visible.indexOf(key) != -1) { + this.selection.push(selection[i]); + } + } + this.render_elements(); } }, - /** Renders the widget. This function also checks for statusbar_colors='{"pending": "blue"}' * attribute in the widget. This allows to set a given color to a given * state (given by the key of (key, label)). */ render_elements: function () { - var content = instance.web.qweb.render("FieldStatus.content", {widget: this, _:_}); - this.$element.html(content); - + var self = this; + var content = instance.web.qweb.render("FieldStatus.content", {widget: this}); + this.$el.html(content); var colors = JSON.parse((this.node.attrs || {}).statusbar_colors || "{}"); var color = colors[this.selected_value]; if (color) { - var elem = this.$element.find("li.oe_form_steps_active span"); - elem.css("color", color); + this.$("oe_active").css("color", color); + } + }, + on_click_stage: function (ev) { + var self = this; + var $li = $(ev.currentTarget); + var val = parseInt($li.data("id")); + if (val != self.selected_value) { + this.view.recursive_save().then(function() { + var change = {}; + change[self.name] = val; + self.view.dataset.write(self.view.datarecord.id, change).then(function() { + self.view.reload(); + }); + }); } }, }); @@ -4235,7 +4933,7 @@ instance.web.form.FieldStatus = instance.web.form.AbstractField.extend({ /** * Registry of form fields, called by :js:`instance.web.FormView`. * - * All referenced classes must implement FieldMixin. Those represent the classes whose instances + * All referenced classes must implement FieldInterface. Those represent the classes whose instances * will substitute to the <field> tags as defined in OpenERP's views. */ instance.web.form.widgets = new instance.web.Registry({ @@ -4244,6 +4942,7 @@ instance.web.form.widgets = new instance.web.Registry({ 'email' : 'instance.web.form.FieldEmail', 'url' : 'instance.web.form.FieldUrl', 'text' : 'instance.web.form.FieldText', + 'html' : 'instance.web.form.FieldTextHtml', 'date' : 'instance.web.form.FieldDate', 'datetime' : 'instance.web.form.FieldDatetime', 'selection' : 'instance.web.form.FieldSelection', @@ -4274,6 +4973,9 @@ instance.web.form.tags = new instance.web.Registry({ 'button' : 'instance.web.form.WidgetButton', }); +instance.web.form.custom_widgets = new instance.web.Registry({ +}); + }; // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/web/static/src/js/view_list.js b/addons/web/static/src/js/view_list.js index de73ec21884..fd34ffddb7b 100644 --- a/addons/web/static/src/js/view_list.js +++ b/addons/web/static/src/js/view_list.js @@ -21,9 +21,6 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi // whether the view rows can be reordered (via vertical drag & drop) 'reorderable': true, 'action_buttons': true, - // if true, the view can't be editable, ignoring the view's and the context's - // instructions - 'read_only': false, }, /** * Core class for list-type displays. @@ -64,7 +61,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi this.records = new Collection(); - this.set_groups(new instance.web.ListView.Groups(this)); + this.set_groups(new (this.options.GroupsType)(this)); if (this.dataset instanceof instance.web.DataSetStatic) { this.groups.datagroup = new instance.web.StaticDataGroup(this.dataset); @@ -87,6 +84,14 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi this.no_leaf = false; }, + set_default_options: function (options) { + this._super(options); + _.defaults(this.options, { + GroupsType: instance.web.ListView.Groups, + ListType: instance.web.ListView.List + }); + }, + /** * Retrieves the view's number of records per page (|| section) * @@ -132,13 +137,13 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi }); }, /** - * View startup method, the default behavior is to set the ``oe-listview`` + * View startup method, the default behavior is to set the ``oe_list`` * class on its root element and to perform an RPC load call. * * @returns {$.Deferred} loading promise */ start: function() { - this.$element.addClass('oe-listview'); + this.$el.addClass('oe_list'); return this.reload_view(null, null, true); }, /** @@ -241,19 +246,26 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi this.setup_columns(this.fields_view.fields, grouped); - this.$element.html(QWeb.render(this._template, this)); + this.$el.html(QWeb.render(this._template, this)); + this.$el.addClass(this.fields_view.arch.attrs['class']); + + // add css classes that reflect the (absence of) access rights + this.$el.toggleClass('oe_list_cannot_create', !this.is_action_enabled('create')) + .toggleClass('oe_list_cannot_edit', !this.is_action_enabled('edit')) + .toggleClass('oe_list_cannot_delete', !this.is_action_enabled('delete')); + // Head hook // Selecting records - this.$element.find('.all-record-selector').click(function(){ - self.$element.find('.oe-record-selector input').prop('checked', - self.$element.find('.all-record-selector').prop('checked') || false); + this.$el.find('.oe_list_record_selector').click(function(){ + self.$el.find('.oe_list_record_selector input').prop('checked', + self.$el.find('.oe_list_record_selector').prop('checked') || false); var selection = self.groups.get_selection(); $(self.groups).trigger( 'selected', [selection.ids, selection.records]); }); // Sorting columns - this.$element.find('thead').delegate('th.oe-sortable[data-id]', 'click', function (e) { + this.$el.find('thead').delegate('th.oe_sortable[data-id]', 'click', function (e) { e.stopPropagation(); var $this = $(this); self.dataset.sort($this.data('id')); @@ -262,26 +274,22 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi } else { $this.toggleClass("sortdown"); } - $this.siblings('.oe-sortable').removeClass("sortup sortdown"); + $this.siblings('.oe_sortable').removeClass("sortup sortdown"); self.reload_content(); }); - // Add button and Import link + // Add button if (!this.$buttons) { this.$buttons = $(QWeb.render("ListView.buttons", {'widget':self})); if (this.options.$buttons) { this.$buttons.appendTo(this.options.$buttons); } else { - this.$element.find('.oe_list_buttons').replaceWith(this.$buttons); + this.$el.find('.oe_list_buttons').replaceWith(this.$buttons); } this.$buttons.find('.oe_list_add') .click(this.proxy('do_add_record')) - .prop('disabled', grouped && this.options.editable); - this.$buttons.on('click', '.oe_list_button_import', function() { - self.on_sidebar_import(); - return false; - }); + .prop('disabled', grouped); } // Pager @@ -290,7 +298,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi if (this.options.$buttons) { this.$pager.appendTo(this.options.$pager); } else { - this.$element.find('.oe_list_pager').replaceWith(this.$pager); + this.$el.find('.oe_list_pager').replaceWith(this.$pager); } this.$pager @@ -314,7 +322,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi self.page = 0; } self.reload_content(); - }).find('.oe-pager-state') + }).find('.oe_list_pager_state') .click(function (e) { e.stopPropagation(); var $this = $(this); @@ -332,6 +340,8 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi self._limit = (isNaN(val) ? null : val); self.page = 0; self.reload_content(); + }).blur(function() { + $(this).trigger('change'); }) .val(self._limit || 'NaN'); }); @@ -341,12 +351,12 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi if (!this.sidebar && this.options.$sidebar) { this.sidebar = new instance.web.Sidebar(this); this.sidebar.appendTo(this.options.$sidebar); - this.sidebar.add_items('other', [ - { label: _t("Import"), callback: this.on_sidebar_import }, + this.sidebar.add_items('other', _.compact([ { label: _t("Export"), callback: this.on_sidebar_export }, - { label: _t('Delete'), callback: this.do_delete_selected }, - ]); + self.is_action_enabled('delete') && { label: _t('Delete'), callback: this.do_delete_selected } + ])); this.sidebar.add_toolbar(this.fields_view.toolbar); + this.sidebar.$el.hide(); } }, /** @@ -364,17 +374,20 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi } var total = dataset.size(); + var limit = this.limit() || total; + this.$pager.toggle(total !== 0); + this.$pager.toggleClass('oe_list_pager_single_page', (total <= limit)); var spager = '-'; if (total) { - var range_start = this.page * this.limit() + 1; - var range_stop = range_start - 1 + this.limit(); + var range_start = this.page * limit + 1; + var range_stop = range_start - 1 + limit; if (range_stop > total) { range_stop = total; } spager = _.str.sprintf('%d-%d of %d', range_start, range_stop, total); } - this.$pager.find('.oe-pager-state').text(spager); + this.$pager.find('.oe_list_pager_state').text(spager); }, /** * Sets up the listview's columns: merges view and fields data, move @@ -385,68 +398,26 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi * @param {Boolean} [grouped] Should the grouping columns (group and count) be displayed */ setup_columns: function (fields, grouped) { - var domain_computer = instance.web.form.compute_domain; - - var noop = function () { return {}; }; - var field_to_column = function (field) { - var name = field.attrs.name; - var column = _.extend({id: name, tag: field.tag}, - fields[name], field.attrs); - // modifiers computer - if (column.modifiers) { - var modifiers = JSON.parse(column.modifiers); - column.modifiers_for = function (fields) { - if (!modifiers.invisible) { - return {}; - } - return { - 'invisible': domain_computer(modifiers.invisible, fields) - }; - }; - if (modifiers['tree_invisible']) { - column.invisible = '1'; - } else { - delete column.invisible; - } - } else { - column.modifiers_for = noop; - } - return column; - }; - + var registry = instance.web.list.columns; + var reorder = this.options.reorderable; this.columns.splice(0, this.columns.length); - this.columns.push.apply( - this.columns, - _(this.fields_view.arch.children).map(field_to_column)); + this.columns.push.apply(this.columns, + _(this.fields_view.arch.children).map(function (field) { + var id = field.attrs.name; + if(field.attrs.widget == 'handle' && !reorder){ + field.attrs.reorderable = reorder || true; + } + return registry.for_(id, fields[id], field); + })); if (grouped) { - this.columns.unshift({ - id: '_group', tag: '', string: _t("Group"), meta: true, - modifiers_for: function () { return {}; } - }, { - id: '_count', tag: '', string: '#', meta: true, - modifiers_for: function () { return {}; } - }); + this.columns.unshift( + new instance.web.list.MetaColumn('_group', _t("Group"))); } this.visible_columns = _.filter(this.columns, function (column) { - return column.invisible !== '1'; + return column.invisible !== '1' && !column.reorderable; }); - - this.aggregate_columns = _(this.visible_columns) - .map(function (column) { - if (column.type !== 'integer' && column.type !== 'float') { - return {}; - } - var aggregation_func = column['group_operator'] || 'sum'; - if (!(aggregation_func in column)) { - return {}; - } - - return _.extend({}, column, { - 'function': aggregation_func, - label: column[aggregation_func] - }); - }); + this.aggregate_columns = _(this.visible_columns).invoke('to_aggregate'); }, /** * Used to handle a click on a table row, if no other handler caught the @@ -472,9 +443,6 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi }, do_show: function () { this._super(); - if (this.sidebar) { - this.sidebar.$element.show(); - } if (this.$buttons) { this.$buttons.show(); } @@ -484,7 +452,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi }, do_hide: function () { if (this.sidebar) { - this.sidebar.$element.hide(); + this.sidebar.$el.hide(); } if (this.$buttons) { this.$buttons.hide(); @@ -508,7 +476,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi if (this.embedded_view) { return $.Deferred().then(callback).resolve(this.embedded_view); } else { - return this.rpc('/web/listview/load', { + return this.rpc('/web/view/load', { model: this.model, view_id: this.view_id, view_type: "tree", @@ -524,10 +492,10 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi */ reload_content: function () { var self = this; - self.$element.find('.all-record-selector').prop('checked', false); + self.$el.find('.oe_list_record_selector').prop('checked', false); this.records.reset(); var reloaded = $.Deferred(); - this.$element.find('.oe-listview-content').append( + this.$el.find('.oe_list_content').append( this.groups.render(function () { if (self.dataset.index == null) { var has_one = false; @@ -548,6 +516,20 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi reload: function () { return this.reload_content(); }, + reload_record: function (record) { + return this.dataset.read_ids( + [record.get('id')], + _.pluck(_(this.columns).filter(function (r) { + return r.tag === 'field'; + }), 'name') + ).then(function (records) { + _(records[0]).each(function (value, key) { + record.set(key, value, {silent: true}); + }); + record.trigger('change', record); + }); + }, + do_load_state: function(state, warm) { var reload = false; if (state.page && this.page !== state.page) { @@ -615,7 +597,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi if (!ids.length) { this.dataset.index = 0; if (this.sidebar) { - this.sidebar.$element.hide(); + this.sidebar.$el.hide(); } this.compute_aggregates(); return; @@ -623,7 +605,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi this.dataset.index = _(this.dataset.ids).indexOf(ids[0]); if (this.sidebar) { - this.sidebar.$element.show(); + this.sidebar.$el.show(); } this.compute_aggregates(_(records).map(function (record) { @@ -638,6 +620,23 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi * @param {Function} callback should be called after the action is executed, if non-null */ do_button_action: function (name, id, callback) { + this.handle_button(name, id, callback); + }, + /** + * Base handling of buttons, can be called when overriding do_button_action + * in order to bypass parent overrides. + * + * The callback will be provided with the ``id`` as its parameter, in case + * handle_button's caller had to alter the ``id`` (or even create one) + * while not being ``callback``'s creator. + * + * This method should not be overridden. + * + * @param {String} name action name + * @param {Object} id id of the record the action should be called on + * @param {Function} callback should be called after the action is executed, if non-null + */ + handle_button: function (name, id, callback) { var action = _.detect(this.columns, function (field) { return field.name === name; }); @@ -656,7 +655,8 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi c.add(action.context); } action.context = c; - this.do_execute_action(action, this.dataset, id, callback); + this.do_execute_action( + action, this.dataset, id, _.bind(callback, null, id)); }, /** * Handles the activation of a record (clicking on it) @@ -765,16 +765,14 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi }, display_aggregates: function (aggregation) { var self = this; - var $footer_cells = this.$element.find('.oe-list-footer'); + var $footer_cells = this.$el.find('.oe_list_footer'); _(this.aggregate_columns).each(function (column) { if (!column['function']) { return; } $footer_cells.filter(_.str.sprintf('[data-field=%s]', column.id)) - .html(instance.web.format_cell(aggregation, column, { - process_modifiers: false - })); + .html(column.format(aggregation, { process_modifiers: false })); }); }, get_selected_ids: function() { @@ -793,7 +791,7 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi pad_columns: function (count, options) { options = options || {}; // padding for action/pager header - var $first_header = this.$element.find('thead tr:first th'); + var $first_header = this.$el.find('thead tr:first th'); var colspan = $first_header.attr('colspan'); if (colspan) { if (!this.previous_colspan) { @@ -802,10 +800,10 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi $first_header.attr('colspan', parseInt(colspan, 10) + count); } // Padding for column titles, footer and data rows - var $rows = this.$element - .find('.oe-listview-header-columns, tr:not(thead tr)') + var $rows = this.$el + .find('.oe_list_header_columns, tr:not(thead tr)') .not(options['except']); - var newcols = new Array(count+1).join('<td class="oe-listview-padding"></td>'); + var newcols = new Array(count+1).join('<td class="oe_list_padding"></td>'); if (options.position === 'before') { $rows.prepend(newcols); } else { @@ -816,27 +814,29 @@ instance.web.ListView = instance.web.View.extend( /** @lends instance.web.ListVi * Removes all padding columns of the table */ unpad_columns: function () { - this.$element.find('.oe-listview-padding').remove(); + this.$el.find('.oe_list_padding').remove(); if (this.previous_colspan) { - this.$element + this.$el .find('thead tr:first th') .attr('colspan', this.previous_colspan); this.previous_colspan = null; } }, no_result: function () { - this.$element.find('.oe_view_nocontent').remove(); + this.$el.find('.oe_view_nocontent').remove(); if (this.groups.group_by || !this.options.action || !this.options.action.help) { return; } - this.$element.find('table:first').hide(); - this.$element.prepend( - $('<div class="oe_view_nocontent">') - .append($('<img>', { src: '/web/static/src/img/view_empty_arrow.png' })) - .append($('<div>').html(this.options.action.help)) + this.$el.find('table:first').hide(); + this.$el.prepend( + $('<div class="oe_view_nocontent">').html(this.options.action.help) ); + var create_nocontent = this.$buttons; + this.$el.find('.oe_view_nocontent').click(function() { + create_nocontent.effect('bounce', {distance: 18, times: 5}, 150); + }); } }); instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web.ListView.List# */{ @@ -882,52 +882,63 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. this.record_callbacks = { 'remove': function (event, record) { - var $row = self.$current.find( - '[data-id=' + record.get('id') + ']'); + var $row = self.$current.children( + '[data-id=' + record.get('id') + ']'); var index = $row.data('index'); $row.remove(); - self.refresh_zebra(index); }, 'reset': function () { return self.on_records_reset(); }, - 'change': function (event, record) { - var $row = self.$current.find('[data-id=' + record.get('id') + ']'); + 'change': function (event, record, attribute, value, old_value) { + var $row; + if (attribute === 'id') { + if (old_value) { + throw new Error("Setting 'id' attribute on existing record " + + JSON.stringify(record.attributes)); + } + if (!_.contains(self.dataset.ids, value)) { + // add record to dataset if not already in (added by + // the form view?) + self.dataset.ids.splice( + self.records.indexOf(record), 0, value); + } + // Set id on new record + $row = self.$current.children('[data-id=false]'); + } else { + $row = self.$current.children( + '[data-id=' + record.get('id') + ']'); + } $row.replaceWith(self.render_record(record)); }, 'add': function (ev, records, record, index) { - var $new_row = $('<tr>').attr({ - 'data-id': record.get('id') - }); + var $new_row = $(self.render_record(record)); if (index === 0) { $new_row.prependTo(self.$current); } else { var previous_record = records.at(index-1), - $previous_sibling = self.$current.find( + $previous_sibling = self.$current.children( '[data-id=' + previous_record.get('id') + ']'); $new_row.insertAfter($previous_sibling); } - - self.refresh_zebra(index, 1); } }; _(this.record_callbacks).each(function (callback, event) { this.records.bind(event, callback); }, this); - this.$_element = $('<tbody>') - .appendTo(document.body) - .delegate('th.oe-record-selector', 'click', function (e) { + this.$current = $('<tbody>') + .delegate('th.oe_list_record_selector', 'click', function (e) { e.stopPropagation(); var selection = self.get_selection(); $(self).trigger( 'selected', [selection.ids, selection.records]); }) - .delegate('td.oe-record-delete button', 'click', function (e) { + .delegate('td.oe_list_record_delete button', 'click', function (e) { e.stopPropagation(); var $row = $(e.target).closest('tr'); $(self).trigger('deleted', [[self.row_id($row)]]); }) - .delegate('td.oe-field-cell button', 'click', function (e) { + .delegate('td.oe_list_field_cell button', 'click', function (e) { e.stopPropagation(); var $target = $(e.currentTarget), field = $target.closest('td').data('field'), @@ -938,19 +949,19 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. // of digits, nice when storing actual numbers, not nice when // storing strings composed only of digits. Force the action // name to be a string - $(self).trigger('action', [field.toString(), record_id, function () { - return self.reload_record(self.records.get(record_id)); + $(self).trigger('action', [field.toString(), record_id, function (id) { + return self.reload_record(self.records.get(id)); }]); }) .delegate('a', 'click', function (e) { e.stopPropagation(); }) .delegate('tr', 'click', function (e) { - e.stopPropagation(); var row_id = self.row_id(e.currentTarget); - if (row_id !== undefined) { + if (row_id) { + e.stopPropagation(); if (!self.dataset.select_id(row_id)) { - throw "Could not find id in dataset" + throw new Error("Could not find id in dataset"); } self.row_clicked(e); } @@ -978,7 +989,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. // to get a correctly displayable value in the field var model = ref_match[1], id = parseInt(ref_match[2], 10); - new instance.web.DataSet(this.view, model).name_get([id], function(names) { + new instance.web.DataSet(this.view, model).name_get([id]).then(function(names) { if (!names.length) { return; } record.set(column.id, names[0][1]); }); @@ -994,29 +1005,46 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. // and let the various registered events handle refreshing the // row new instance.web.DataSet(this.view, column.relation) - .name_get([value], function (names) { + .name_get([value]).then(function (names) { if (!names.length) { return; } record.set(column.id, names[0]); }); } + } else if (column.type === 'many2many') { + value = record.get(column.id); + // non-resolved (string) m2m values are arrays + if (value instanceof Array && !_.isEmpty(value)) { + var ids; + // they come in two shapes: + if (value[0] instanceof Array) { + var command = value[0]; + // 1. an array of m2m commands (usually (6, false, ids)) + if (command[0] !== 6) { + throw new Error(_t("Unknown m2m command ") + command[0]); + } + ids = command[2]; + } else { + // 2. an array of ids + ids = value; + } + new instance.web.Model(column.relation) + .call('name_get', [ids]).then(function (names) { + record.set(column.id, _(names).pluck(1).join(', ')); + }) + } } - return instance.web.format_cell(record.toForm().data, column, { + return column.format(record.toForm().data, { model: this.dataset.model, id: record.get('id') }); }, render: function () { - var self = this; - if (this.$current) { - this.$current.remove(); - } - this.$current = this.$_element.clone(true); this.$current.empty().append( QWeb.render('ListView.rows', _.extend({ render_cell: function () { return self.render_cell.apply(self, arguments); } }, this))); - this.pad_table_to(5); + this.pad_table_to(4); }, pad_table_to: function (count) { if (this.records.length >= count || @@ -1025,20 +1053,20 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. } var cells = []; if (this.options.selectable) { - cells.push('<th class="oe-record-selector"></td>'); + cells.push('<th class="oe_list_record_selector"></td>'); } _(this.columns).each(function(column) { if (column.invisible === '1') { return; } if (column.tag === 'button') { - cells.push('<td class="oe-button" title="' + column.string + '"> </td>'); + cells.push('<td class="oe_button" title="' + column.string + '"> </td>'); } else { cells.push('<td title="' + column.string + '"> </td>'); } }); if (this.options.deletable) { - cells.push('<td class="oe-record-delete"><button type="button" style="visibility: hidden"> </button></td>'); + cells.push('<td class="oe_list_record_delete"><button type="button" style="visibility: hidden"> </button></td>'); } cells.unshift('<tr>'); cells.push('</tr>'); @@ -1047,19 +1075,18 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. this.$current .children('tr:not([data-id])').remove().end() .append(new Array(count - this.records.length + 1).join(row)); - this.refresh_zebra(this.records.length); }, /** * Gets the ids of all currently selected records, if any * @returns {Object} object with the keys ``ids`` and ``records``, holding respectively the ids of all selected records and the records themselves. */ get_selection: function () { + var result = {ids: [], records: []}; if (!this.options.selectable) { - return []; + return result; } var records = this.records; - var result = {ids: [], records: []}; - this.$current.find('th.oe-record-selector input:checked') + this.$current.find('th.oe_list_record_selector input:checked') .closest('tr').each(function () { var record = records.get($(this).data('id')); result.ids.push(record.get('id')); @@ -1087,7 +1114,6 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. if (!this.$current) { return; } this.$current.remove(); this.$current = null; - this.$_element.remove(); }, get_records: function () { return this.records.map(function (record) { @@ -1101,17 +1127,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. * @returns {$.Deferred} promise to the finalization of the reloading */ reload_record: function (record) { - return this.dataset.read_ids( - [record.get('id')], - _.pluck(_(this.columns).filter(function (r) { - return r.tag === 'field'; - }), 'name') - ).then(function (records) { - _(records[0]).each(function (value, key) { - record.set(key, value, {silent: true}); - }); - record.trigger('change', record); - }); + return this.view.reload_record(record); }, /** * Renders a list record to HTML @@ -1120,6 +1136,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. * @returns {String} QWeb rendering of the selected record */ render_record: function (record) { + var self = this; var index = this.records.indexOf(record); return QWeb.render('ListView.row', { columns: this.columns, @@ -1128,26 +1145,7 @@ instance.web.ListView.List = instance.web.Class.extend( /** @lends instance.web. row_parity: (index % 2 === 0) ? 'even' : 'odd', view: this.view, render_cell: function () { - return this.render_cell.apply(this, arguments); } - }); - }, - /** - * Fixes fixes the even/odd classes - * - * @param {Number} [from_index] index from which to resequence - * @param {Number} [offset = 0] selection offset for DOM, in case there are rows to ignore in the table - */ - refresh_zebra: function (from_index, offset) { - offset = offset || 0; - from_index = from_index || 0; - var dom_offset = offset + from_index; - var sel = dom_offset ? ':gt(' + (dom_offset - 1) + ')' : null; - this.$current.children(sel).each(function (i, e) { - var index = from_index + i; - // reset record-index accelerators on rows and even/odd - var even = index%2 === 0; - $(e).toggleClass('even', even) - .toggleClass('odd', !even); + return self.render_cell.apply(self, arguments); } }); } }); @@ -1236,7 +1234,7 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we }); this.$row.children().last() .append($prev) - .append('<span class="oe-pager-state"></span>') + .append('<span class="oe_list_pager_state"></span>') .append($next); }, open: function (point_insertion) { @@ -1274,7 +1272,7 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we self.records.proxy(group.value).reset(); delete self.children[group.value]; } - var child = self.children[group.value] = new instance.web.ListView.Groups(self.view, { + var child = self.children[group.value] = new (self.view.options.GroupsType)(self.view, { records: self.records.proxy(group.value), options: self.options, columns: self.columns @@ -1282,7 +1280,7 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we self.bind_child_events(child); child.datagroup = group; - var $row = child.$row = $('<tr>'); + var $row = child.$row = $('<tr class="oe_group_header">'); if (group.openable && group.length) { $row.click(function (e) { if (!$row.data('open')) { @@ -1302,22 +1300,30 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we } placeholder.appendChild($row[0]); - var $group_column = $('<th class="oe-group-name">').appendTo($row); + var $group_column = $('<th class="oe_list_group_name">').appendTo($row); // Don't fill this if group_by_no_leaf but no group_by if (group.grouped_on) { var row_data = {}; row_data[group.grouped_on] = group; var group_column = _(self.columns).detect(function (column) { return column.id === group.grouped_on; }); + if (! group_column) { + throw new Error(_.str.sprintf( + _t("Grouping on field '%s' is not possible because that field does not appear in the list view."), + group.grouped_on)); + } + var group_label; try { - $group_column.html(instance.web.format_cell( - row_data, group_column, { - value_if_empty: _t("Undefined"), - process_modifiers: false - })); + group_label = group_column.format(row_data, { + value_if_empty: _t("Undefined"), + process_modifiers: false + }); } catch (e) { - $group_column.html(row_data[group_column.id].value); + group_label = row_data[group_column.id].value; } + $group_column.text(_.str.sprintf("%s (%d)", + group_label, group.length)); + if (group.length && group.openable) { // Make openable if not terminal group & group_by_no_leaf $group_column.prepend('<span class="ui-icon ui-icon-triangle-1-e" style="float: left;">'); @@ -1329,14 +1335,12 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we } } self.indent($group_column, group.level); - // count column - $('<td>').text(group.length).appendTo($row); if (self.options.selectable) { $row.append('<td>'); } _(self.columns).chain() - .filter(function (column) {return !column.invisible;}) + .filter(function (column) { return column.invisible !== '1'; }) .each(function (column) { if (column.meta) { // do not do anything @@ -1344,15 +1348,14 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we var r = {}; r[column.id] = {value: group.aggregates[column.id]}; $('<td class="oe_number">') - .html(instance.web.format_cell( - r, column, {process_modifiers: false})) + .html(column.format(r, {process_modifiers: false})) .appendTo($row); } else { $row.append('<td>'); } }); if (self.options.deletable) { - $row.append('<td class="oe-group-pagination">'); + $row.append('<td class="oe_list_group_pagination">'); } }); return placeholder; @@ -1377,7 +1380,7 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we }, render_dataset: function (dataset) { var self = this, - list = new instance.web.ListView.List(this, { + list = new (this.view.options.ListType)(this, { options: this.options, columns: this.columns, dataset: dataset, @@ -1403,11 +1406,11 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we } else { if (dataset.size() == records.length) { // only one page - self.$row.find('td.oe-group-pagination').empty(); + self.$row.find('td.oe_list_group_pagination').empty(); } else { var pages = Math.ceil(dataset.size() / limit); self.$row - .find('.oe-pager-state') + .find('.oe_list_pager_state') .text(_.str.sprintf(_t("%(page)d/%(page_count)d"), { page: page + 1, page_count: pages @@ -1432,18 +1435,31 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we }, setup_resequence_rows: function (list, dataset) { // drag and drop enabled if list is not sorted and there is a - // "sequence" column in the view. + // visible column with @widget=handle or "sequence" column in the view. if ((dataset.sort && dataset.sort()) || !_(this.columns).any(function (column) { - return column.name === 'sequence'; })) { + return column.widget === 'handle' + || column.name === 'sequence'; })) { return; } + var sequence_field = _(this.columns).find(function (c) { + return c.widget === 'handle'; + }); + var seqname = sequence_field ? sequence_field.name : 'sequence'; + // ondrop, move relevant record & fix sequences list.$current.sortable({ axis: 'y', items: '> tr[data-id]', - containment: 'parent', - helper: 'clone', + helper: 'clone' + }); + if (sequence_field) { + list.$current.sortable('option', 'handle', '.oe_list_field_handle'); + } + list.$current.sortable('option', { + start: function (e, ui) { + ui.placeholder.height(ui.item.height()); + }, stop: function (event, ui) { var to_move = list.records.get(ui.item.data('id')), target_id = ui.item.prev().data('id'), @@ -1461,7 +1477,7 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we var record, index = to, // if drag to 1st row (to = 0), start sequencing from 0 // (exclusive lower bound) - seq = to ? list.records.at(to - 1).get('sequence') : 0; + seq = to ? list.records.at(to - 1).get(seqname) : 0; while (++seq, record = list.records.at(index++)) { // write are independent from one another, so we can just // launch them all at the same time and we don't really @@ -1471,39 +1487,39 @@ instance.web.ListView.Groups = instance.web.Class.extend( /** @lends instance.we // when synchronous (without setTimeout) (function (dataset, id, seq) { $.async_when().then(function () { - dataset.write(id, {sequence: seq}); + var attrs = {}; + attrs[seqname] = seq; + dataset.write(id, attrs); }); }(dataset, record.get('id'), seq)); - record.set('sequence', seq); + record.set(seqname, seq); } - - list.refresh_zebra(); } }); }, render: function (post_render) { var self = this; - var $element = $('<tbody>'); - this.elements = [$element[0]]; + var $el = $('<tbody>'); + this.elements = [$el[0]]; this.datagroup.list( _(this.view.visible_columns).chain() .filter(function (column) { return column.tag === 'field' }) .pluck('name').value(), function (groups) { - $element[0].appendChild( + $el[0].appendChild( self.render_groups(groups)); if (post_render) { post_render(); } }, function (dataset) { self.render_dataset(dataset).then(function (list) { self.children[null] = list; self.elements = - [list.$current.replaceAll($element)[0]]; + [list.$current.replaceAll($el)[0]]; self.setup_resequence_rows(list, dataset); if (post_render) { post_render(); } }); }); - return $element; + return $el; }, /** * Returns the ids of all selected records for this group, and the records @@ -1807,7 +1823,7 @@ var Collection = instance.web.Class.extend(/** @lends Collection# */{ * @returns this */ remove: function (record) { - var index = _(this.records).indexOf(record); + var index = this.indexOf(record); if (index === -1) { _(this._proxies).each(function (proxy) { proxy.remove(record); @@ -1823,13 +1839,42 @@ var Collection = instance.web.Class.extend(/** @lends Collection# */{ return this; }, - _onRecordEvent: function (event, record, options) { + _onRecordEvent: function (event) { + switch(event) { // don't propagate reset events - if (event === 'reset') { return; } + case 'reset': return; + case 'change:id': + var record = arguments[1]; + var new_value = arguments[2]; + var old_value = arguments[3]; + // [change:id, record, new_value, old_value] + if (this._byId[old_value] === record) { + delete this._byId[old_value]; + this._byId[new_value] = record; + } + break; + } this.trigger.apply(this, arguments); }, // underscore-type methods + find: function (callback) { + var record; + for(var section in this._proxies) { + if (!this._proxies.hasOwnProperty(section)) { + continue + } + if ((record = this._proxies[section].find(callback))) { + return record; + } + } + for(var i=0; i<this.length; ++i) { + record = this.records[i]; + if (callback(record)) { + return record; + } + } + }, each: function (callback) { for(var section in this._proxies) { if (this._proxies.hasOwnProperty(section)) { @@ -1854,6 +1899,46 @@ var Collection = instance.web.Class.extend(/** @lends Collection# */{ }, indexOf: function (record) { return _(this.records).indexOf(record); + }, + succ: function (record, options) { + options = options || {wraparound: false}; + var result; + for(var section in this._proxies) { + if (!this._proxies.hasOwnProperty(section)) { + continue; + } + if ((result = this._proxies[section].succ(record, options))) { + return result; + } + } + var index = this.indexOf(record); + if (index === -1) { return null; } + var next_index = index + 1; + if (options.wraparound && (next_index === this.length)) { + return this.at(0); + } + return this.at(next_index); + }, + pred: function (record, options) { + options = options || {wraparound: false}; + + var result; + for (var section in this._proxies) { + if (!this._proxies.hasOwnProperty(section)) { + continue; + } + if ((result = this._proxies[section].pred(record, options))) { + return result; + } + } + + var index = this.indexOf(record); + if (index === -1) { return null; } + var next_index = index - 1; + if (options.wraparound && (next_index === -1)) { + return this.at(this.length - 1); + } + return this.at(next_index); } }); Collection.include(Events); @@ -1861,6 +1946,213 @@ instance.web.list = { Events: Events, Record: Record, Collection: Collection -} +}; +/** + * Registry for column objects used to format table cells (and some other tasks + * e.g. aggregation computations). + * + * Maps a field or button to a Column type via its ``$tag.$widget``, + * ``$tag.$type`` or its ``$tag`` (alone). + * + * This specific registry has a dedicated utility method ``for_`` taking a + * field (from fields_get/fields_view_get.field) and a node (from a view) and + * returning the right object *already instantiated from the data provided*. + * + * @type {instance.web.Registry} + */ +instance.web.list.columns = new instance.web.Registry({ + 'field': 'instance.web.list.Column', + 'field.boolean': 'instance.web.list.Boolean', + 'field.binary': 'instance.web.list.Binary', + 'field.progressbar': 'instance.web.list.ProgressBar', + 'field.handle': 'instance.web.list.Handle', + 'button': 'instance.web.list.Button', +}); +instance.web.list.columns.for_ = function (id, field, node) { + var description = _.extend({tag: node.tag}, field, node.attrs); + var tag = description.tag; + var Type = this.get_any([ + tag + '.' + description.widget, + tag + '.'+ description.type, + tag + ]); + return new Type(id, node.tag, description) +}; + +instance.web.list.Column = instance.web.Class.extend({ + init: function (id, tag, attrs) { + _.extend(attrs, { + id: id, + tag: tag + }); + + this.modifiers = attrs.modifiers ? JSON.parse(attrs.modifiers) : {}; + delete attrs.modifiers; + _.extend(this, attrs); + + if (this.modifiers['tree_invisible']) { + this.invisible = '1'; + } else { delete this.invisible; } + }, + modifiers_for: function (fields) { + var out = {}; + var domain_computer = instance.web.form.compute_domain; + + for (var attr in this.modifiers) { + if (!this.modifiers.hasOwnProperty(attr)) { continue; } + var modifier = this.modifiers[attr]; + out[attr] = _.isBoolean(modifier) + ? modifier + : domain_computer(modifier, fields); + } + + return out; + }, + to_aggregate: function () { + if (this.type !== 'integer' && this.type !== 'float') { + return {}; + } + var aggregation_func = this['group_operator'] || 'sum'; + if (!(aggregation_func in this)) { + return {}; + } + var C = function (fn, label) { + this['function'] = fn; + this.label = label; + }; + C.prototype = this; + return new C(aggregation_func, this[aggregation_func]); + }, + /** + * + * @param row_data record whose values should be displayed in the cell + * @param {Object} [options] + * @param {String} [options.value_if_empty=''] what to display if the field's value is ``false`` + * @param {Boolean} [options.process_modifiers=true] should the modifiers be computed ? + * @param {String} [options.model] current record's model + * @param {Number} [options.id] current record's id + * @return {String} + */ + format: function (row_data, options) { + options = options || {}; + var attrs = {}; + if (options.process_modifiers !== false) { + attrs = this.modifiers_for(row_data); + } + if (attrs.invisible) { return ''; } + + if (!row_data[this.id]) { + return options.value_if_empty === undefined + ? '' + : options.value_if_empty; + } + return this._format(row_data, options); + }, + /** + * Method to override in order to provide alternative HTML content for the + * cell. Column._format will simply call ``instance.web.format_value`` and + * escape the output. + * + * The output of ``_format`` will *not* be escaped by ``format``, any + * escaping *must be done* by ``format``. + * + * @private + */ + _format: function (row_data, options) { + return _.escape(instance.web.format_value( + row_data[this.id].value, this, options.value_if_empty)); + } +}); +instance.web.list.MetaColumn = instance.web.list.Column.extend({ + meta: true, + init: function (id, string) { + this._super(id, '', {string: string}); + } +}); +instance.web.list.Button = instance.web.list.Column.extend({ + /** + * Return an actual ``<button>`` tag + */ + format: function (row_data, options) { + options = options || {}; + var attrs = {}; + if (options.process_modifiers !== false) { + attrs = this.modifiers_for(row_data); + } + if (attrs.invisible) { return ''; } + + return QWeb.render('ListView.row.button', { + widget: this, + prefix: instance.session.prefix, + disabled: attrs.readonly + || isNaN(row_data.id.value) + || instance.web.BufferedDataSet.virtual_id_regex.test(row_data.id.value) + }); + } +}); +instance.web.list.Boolean = instance.web.list.Column.extend({ + /** + * Return a potentially disabled checkbox input + * + * @private + */ + _format: function (row_data, options) { + return _.str.sprintf('<input type="checkbox" %s disabled="disabled"/>', + row_data[this.id].value ? 'checked="checked"' : ''); + } +}); +instance.web.list.Binary = instance.web.list.Column.extend({ + /** + * Return a link to the binary data as a file + * + * @private + */ + _format: function (row_data, options) { + var text = _t("Download"); + var download_url = _.str.sprintf( + '/web/binary/saveas?session_id=%s&model=%s&field=%s&id=%d', + instance.session.session_id, options.model, this.id, options.id); + if (this.filename) { + download_url += '&filename_field=' + this.filename; + if (row_data[this.filename]) { + text = _.str.sprintf(_t("Download \"%s\""), instance.web.format_value( + row_data[this.filename].value, {type: 'char'})); + } + } + return _.template('<a href="<%-href%>"><%-text%></a> (%<-size%>)', { + text: text, + href: download_url, + size: row_data[this.id].value + }); + } +}); +instance.web.list.ProgressBar = instance.web.list.Column.extend({ + /** + * Return a formatted progress bar display + * + * @private + */ + _format: function (row_data, options) { + return _.template( + '<progress value="<%-value%>" max="100"><%-value%>%</progress>', { + value: _.str.sprintf("%.0f", row_data[this.id].value || 0) + }); + } +}); +instance.web.list.Handle = instance.web.list.Column.extend({ + init: function () { + this._super.apply(this, arguments); + // Handle overrides the field to not be form-editable. + this.modifiers.readonly = true; + }, + /** + * Return styling hooks for a drag handle + * + * @private + */ + _format: function (row_data, options) { + return '<div class="oe_list_handle">'; + } +}); }; // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/web/static/src/js/view_list_editable.js b/addons/web/static/src/js/view_list_editable.js index c80509a7b2b..892221a347b 100644 --- a/addons/web/static/src/js/view_list_editable.js +++ b/addons/web/static/src/js/view_list_editable.js @@ -3,10 +3,6 @@ * @namespace */ openerp.web.list_editable = function (instance) { - var KEY_RETURN = 13, - KEY_ESCAPE = 27; - var QWeb = instance.web.qweb; - // editability status of list rows instance.web.ListView.prototype.defaults.editable = null; @@ -15,6 +11,15 @@ openerp.web.list_editable = function (instance) { init: function () { var self = this; this._super.apply(this, arguments); + + this._force_editability = null; + this._context_editable = false; + this.editor = this.make_editor(); + // Stores records of {field, cell}, allows for re-rendering fields + // depending on cell state during and after resize events + this.fields_for_resize = []; + instance.web.bus.on('resize', this, this.resize_fields); + $(this.groups).bind({ 'edit': function (e, id, dataset) { self.do_edit(dataset.index, id, dataset); @@ -26,7 +31,35 @@ openerp.web.list_editable = function (instance) { self.configure_pager(self.dataset); self.compute_aggregates(); } - }) + }); + + this.records.bind('remove', function () { + if (self.editor.is_editing()) { + self.cancel_edition(); + } + }); + + this.on('edit:before', this, function (event) { + if (!self.editable() || self.editor.is_editing()) { + event.cancel = true; + } + }); + this.on('edit:after', this, function () { + self.$el.add(self.$buttons).addClass('oe_editing'); + }); + this.on('save:after cancel:after', this, function () { + self.$el.add(self.$buttons).removeClass('oe_editing'); + }); + }, + destroy: function () { + instance.web.bus.off('resize', this, this.resize_fields); + this._super(); + }, + do_hide: function () { + if (this.editor.is_editing()) { + this.cancel_edition(true); + } + this._super(); }, /** * Handles the activation of a record in editable mode (making a record @@ -42,25 +75,16 @@ openerp.web.list_editable = function (instance) { do_edit: function (index, id, dataset) { _.extend(this.dataset, dataset); }, - /** - * Sets editability status for the list, based on defaults, view - * architecture and the provided flag, if any. - * - * @param {Boolean} [force] forces the list to editability. Sets new row edition status to "bottom". - */ - set_editable: function (force) { - // If ``force``, set editability to bottom - // otherwise rely on view default - // view' @editable is handled separately as we have not yet - // fetched and processed the view at this point. - this.options.editable = ( - ! this.options.read_only && ((force && "bottom") || this.defaults.editable)); + editable: function () { + return this.fields_view.arch.attrs.editable + || this._context_editable + || this.options.editable; }, /** * Replace do_search to handle editability process */ do_search: function(domain, context, group_by) { - this.set_editable(context['set_editable']); + this._context_editable = !!context.set_editable; this._super.apply(this, arguments); }, /** @@ -68,18 +92,66 @@ openerp.web.list_editable = function (instance) { * as an editable row at the top or bottom of the list) */ do_add_record: function () { - if (this.options.editable) { - this.$element.find('table:first').show(); - this.$element.find('.oe_view_nocontent').remove(); - this.groups.new_record(); + if (this.editable()) { + this.$el.find('table:first').show(); + this.$el.find('.oe_view_nocontent').remove(); + this.start_edition(); } else { this._super(); } }, on_loaded: function (data, grouped) { + var self = this; // tree/@editable takes priority on everything else if present. - this.options.editable = ! this.options.read_only && (data.arch.attrs.editable || this.options.editable); - return this._super(data, grouped); + var result = this._super(data, grouped); + if (this.editable()) { + this.$el.addClass('oe_list_editable'); + // FIXME: any hook available to ensure this is only done once? + this.$buttons + .off('click', '.oe_list_save') + .on('click', '.oe_list_save', this.proxy('save_edition')) + .off('click', '.oe_list_discard') + .on('click', '.oe_list_discard', function (e) { + e.preventDefault(); + self.cancel_edition(); + }); + this.$el + .off('click', 'tbody td:not(.oe_list_field_cell)') + .on('click', 'tbody td:not(.oe_list_field_cell)', function () { + if (!self.editor.is_editing()) { + self.start_edition(); + } + }); + this.editor.destroy(); + // Editor is not restartable due to formview not being + // restartable + this.editor = this.make_editor(); + var editor_ready = this.editor.prependTo(this.$el) + .then(this.proxy('setup_events')); + + return $.when(result, editor_ready); + } else { + this.$el.removeClass('oe_list_editable'); + } + + return result; + }, + /** + * Builds a new editor object + * + * @return {instance.web.list.Editor} + */ + make_editor: function () { + return new instance.web.list.Editor(this); + }, + do_button_action: function (name, id, callback) { + var self = this, args = arguments; + this.ensure_saved().then(function (done) { + if (!id && done.created) { + id = done.record.get('id'); + } + self.handle_button.call(self, name, id, callback); + }); }, /** * Ensures the editable list is saved (saves any pending edition if @@ -90,338 +162,627 @@ openerp.web.list_editable = function (instance) { * @returns {$.Deferred} */ ensure_saved: function () { - return this.groups.ensure_saved(); - } - }); - - instance.web.ListView.Groups.include(/** @lends instance.web.ListView.Groups# */{ - passtrough_events: instance.web.ListView.Groups.prototype.passtrough_events + " edit saved", - new_record: function () { - // TODO: handle multiple children - this.children[null].new_record(); + if (!this.editor.is_editing()) { + return $.when(); + } + return this.save_edition(); }, /** - * Ensures descendant editable List instances are all saved if they have - * pending editions. + * Set up the edition of a record of the list view "inline" * - * @returns {$.Deferred} + * @param {instance.web.list.Record} [record] record to edit, leave empty to create a new record + * @param {Object} [options] + * @param {String} [options.focus_field] field to focus at start of edition + * @return {jQuery.Deferred} */ - ensure_saved: function () { - return $.when.apply(null, - _.invoke( - _.values(this.children), - 'ensure_saved')); - } - }); - - instance.web.ListView.List.include(/** @lends instance.web.ListView.List# */{ - row_clicked: function (event) { - if (!this.options.editable) { - return this._super.apply(this, arguments); + start_edition: function (record, options) { + var self = this; + var item = false; + if (record) { + item = record.attributes; + } else { + var attrs = {id: false}; + _(this.columns).chain() + .filter(function (x) { return x.tag === 'field'}) + .pluck('name') + .each(function (field) { attrs[field] = false; }); + record = new instance.web.list.Record(attrs); + this.records.add(record, { + at: this.prepends_on_create() ? 0 : null}); } - this.edit_record($(event.currentTarget).data('id')); + var $recordRow = this.groups.get_row_for(record); + var cells = this.get_cells_for($recordRow); + + return this.ensure_saved().pipe(function () { + self.fields_for_resize.splice(0, self.fields_for_resize.length); + return self.with_event('edit', { + record: record.attributes, + cancel: false + }, function () { + return self.editor.edit(item, function (field_name, field) { + var cell = cells[field_name]; + if (!cell || field.get('effective_readonly')) { + // Readonly fields can just remain the list's, + // form's usually don't have backgrounds &al + field.set({invisible: true}); + return; + } + + // FIXME: need better way to get the field back from bubbling (delegated) DOM events somehow + field.$el.attr('data-fieldname', field_name); + self.fields_for_resize.push({field: field, cell: cell}); + }, options).pipe(function () { + $recordRow.addClass('oe_edition'); + self.resize_fields(); + return record.attributes; + }); + }).fail(function () { + // if the start_edition event is cancelled and it was a + // creation, remove the newly-created empty record + if (!record.get('id')) { + self.records.remove(record); + } + }); + }); + }, + get_cells_for: function ($row) { + var cells = {}; + $row.children('td').each(function (index, el) { + cells[el.getAttribute('data-field')] = el + }); + return cells; }, /** - * Checks if a record is being edited, and if so cancels it + * If currently editing a row, resizes all registered form fields based + * on the corresponding row cell */ - cancel_pending_edition: function () { - var self = this, cancelled = $.Deferred(); - if (!this.edition) { - cancelled.resolve(); - return cancelled.promise(); - } - - if (this.edition_id != null) { - this.reload_record(self.records.get(this.edition_id)).then(function () { - cancelled.resolve(); - }); - } else { - cancelled.resolve(); + resize_fields: function () { + if (!this.editor.is_editing()) { return; } + for(var i=0, len=this.fields_for_resize.length; i<len; ++i) { + var item = this.fields_for_resize[i]; + this.resize_field(item.field, item.cell); } - cancelled.then(function () { - self.view.unpad_columns(); - self.edition_form.destroy(); - self.edition_form.$element.remove(); - delete self.edition_form; - delete self.edition_id; - delete self.edition; - }); - this.pad_table_to(5); - return cancelled.promise(); }, /** - * Adapts this list's view description to be suitable to the inner form - * view of a row being edited. + * Resizes a field's root element based on the corresponding cell of + * a listview row * - * @returns {Object} fields_view_get's view section suitable for putting into form view of editable rows. + * @param {instance.web.form.AbstractField} field + * @param {jQuery} cell */ - get_form_fields_view: function () { - // deep copy of view - var view = $.extend(true, {}, this.group.view.fields_view); - _(view.arch.children).each(function (widget) { - widget.attrs.nolabel = true; - if (widget.tag === 'button') { - delete widget.attrs.string; - } + resize_field: function (field, cell) { + var $cell = $(cell); + var position = $cell.position(); + + // jquery does not understand !important + field.$el.attr('style', 'width: '+$cell.outerWidth()+'px !important'); + field.$el.css({ + top: position.top, + left: position.left, + minHeight: $cell.outerHeight() }); - view.arch.attrs.col = 2 * view.arch.children.length; - return view; }, - on_row_keyup: function (e) { + /** + * @return {jQuery.Deferred} + */ + save_edition: function () { var self = this; - switch (e.which) { - case KEY_RETURN: - this.save_row().then(function (result) { - if (result.created) { - self.new_record(); - return; - } - - var next_record_id, - next_record = self.records.at( - self.records.indexOf(result.edited_record) + 1); - if (next_record) { - next_record_id = next_record.get('id'); - self.dataset.index = _(self.dataset.ids) - .indexOf(next_record_id); - } else { - self.dataset.index = 0; - next_record_id = self.records.at(0).get('id'); + return this.with_event('save', { + editor: this.editor, + form: this.editor.form, + cancel: false + }, function () { + return this.editor.save().pipe(function (attrs) { + var created = false; + var record = self.records.get(attrs.id); + if (!record) { + // new record + created = true; + record = self.records.find(function (r) { + return !r.get('id'); + }).set('id', attrs.id); } - self.edit_record(next_record_id); + // onwrite callback could be altering & reloading the + // record which has *just* been saved, so first perform all + // onwrites then do a final reload of the record + return self.handle_onwrite(record) + .pipe(function () { + return self.reload_record(record); }) + .pipe(function () { + return { created: created, record: record }; }); }); - break; - case KEY_ESCAPE: - this.cancel_edition(); - break; - } + }); }, - render_row_as_form: function (row) { + /** + * @param {Boolean} [force=false] discards the data even if the form has been edited + * @return {jQuery.Deferred} + */ + cancel_edition: function (force) { var self = this; - this.cancel_pending_edition().then(function () { - var record_id = $(row).data('id'); - var $new_row = $('<tr>', { - id: _.uniqueId('oe-editable-row-'), - 'data-id': record_id, - 'class': (row ? $(row).attr('class') : '') + ' oe_form', - click: function (e) {e.stopPropagation();} - }) - .delegate('button.oe-edit-row-save', 'click', function () { - self.save_row(); - }) - .delegate('button', 'keyup', function (e) { - e.stopImmediatePropagation(); - }) - .keyup(function () { - return self.on_row_keyup.apply(self, arguments); }) - .keydown(function (e) { e.stopPropagation(); }); - if (row) { - $new_row.replaceAll(row); - } else if (self.options.editable) { - var $last_child = self.$current.children('tr:last'); - if (self.records.length) { - if (self.options.editable === 'top') { - $new_row.insertBefore( - self.$current.children('[data-id]:first')); - } else { - $new_row.insertAfter( - self.$current.children('[data-id]:last')); + return this.with_event('cancel', { + editor: this.editor, + form: this.editor.form, + cancel: false + }, function () { + return this.editor.cancel(force).pipe(function (attrs) { + if (attrs.id) { + var record = self.records.get(attrs.id); + if (!record) { + // Record removed by third party during edition + return } - } else { - $new_row.prependTo(self.$current); - } - if ($last_child.is(':not([data-id])')) { - $last_child.remove(); + return self.reload_record(record); } - } - self.edition = true; - self.edition_id = record_id; - $new_row.addClass("oe_form_container"); - self.edition_form = new instance.web.ListEditableFormView(self.view, self.dataset, false); - self.edition_form.$element = $new_row; - self.edition_form.editable_list = self; - // HO HO - // empty - $.when(self.edition_form.on_loaded(self.get_form_fields_view())).then(function () { - // put in $.when just in case FormView.on_loaded becomes asynchronous - $new_row.find('> td') - .end() - .find('td:last').removeClass('oe-field-cell').end(); - // pad in case of groupby - _(self.columns).each(function (column) { - if (column.meta) { - $new_row.prepend('<td>'); - } + var to_delete = self.records.find(function (r) { + return !r.get('id'); }); - // Add column for the save, if - // there is none in the list - if (!self.options.deletable) { - self.view.pad_columns( - 1, {except: $new_row}); + if (to_delete) { + self.records.remove(to_delete); } - - self.edition_form.do_show(); }); }); }, - handle_onwrite: function (source_record_id) { + /** + * Executes an action on the view's editor bracketed by a cancellable + * event of the name provided. + * + * The event name provided will be post-fixed with ``:before`` and + * ``:after``, the ``event`` parameter will be passed alongside the + * ``:before`` variant and if the parameter's ``cancel`` key is set to + * ``true`` the action *will not be called* and the method will return + * a rejection + * + * @param {String} event_name name of the event + * @param {Object} event event object, provided to ``:before`` sub-event + * @param {Function} action callable, called with the view's editor as its context + * @param {Array} [args] supplementary arguments provided to the action + * @param {Array} [trigger_params] supplementary arguments provided to the ``:after`` sub-event, before anything fetched by the ``action`` function + * @return {jQuery.Deferred} + */ + with_event: function (event_name, event, action) { var self = this; - var on_write_callback = self.view.fields_view.arch.attrs.on_write; - if (!on_write_callback) { return; } - this.dataset.call(on_write_callback, [source_record_id], function (ids) { - _(ids).each(function (id) { - var record = self.records.get(id); - if (!record) { - // insert after the source record - var index = self.records.indexOf( - self.records.get(source_record_id)) + 1; - record = new instance.web.list.Record({id: id}); - self.records.add(record, {at: index}); - self.dataset.ids.splice(index, 0, id); + event = event || {}; + this.trigger(event_name + ':before', event); + if (event.cancel) { + return $.Deferred().reject({ + message: _.str.sprintf("Event %s:before cancelled", + event_name)}); + } + return $.when(action.call(this)).then(function () { + self.trigger.apply(self, [event_name + ':after'] + .concat(_.toArray(arguments))); + }); + }, + edition_view: function (editor) { + var view = $.extend(true, {}, this.fields_view); + view.arch.tag = 'form'; + _.extend(view.arch.attrs, { + 'class': 'oe_form_container', + version: '7.0' + }); + _(view.arch.children).chain() + .zip(this.columns) + .each(function (ar) { + var widget = ar[0], column = ar[1]; + var modifiers = _.extend({}, column.modifiers); + widget.attrs.nolabel = true; + if (modifiers['tree_invisible'] || widget.tag === 'button') { + modifiers.invisible = true; } - self.reload_record(record); + widget.attrs.modifiers = JSON.stringify(modifiers); + }); + return view; + }, + handle_onwrite: function (source_record) { + var self = this; + var on_write_callback = self.fields_view.arch.attrs.on_write; + if (!on_write_callback) { return $.when(); } + return this.dataset.call(on_write_callback, [source_record.get('id')]) + .pipe(function (ids) { + return $.when.apply( + null, _(ids).map( + _.bind(self.handle_onwrite_record, self, source_record))); }); + }, + handle_onwrite_record: function (source_record, id) { + var record = this.records.get(id); + if (!record) { + // insert after the source record + var index = this.records.indexOf(source_record) + 1; + record = new instance.web.list.Record({id: id}); + this.records.add(record, {at: index}); + this.dataset.ids.splice(index, 0, id); + } + return this.reload_record(record); + }, + prepends_on_create: function () { + return this.editable() === 'top'; + }, + setup_events: function () { + var self = this; + this.editor.$el.on('keyup keydown', function (e) { + if (!self.editor.is_editing()) { return; } + var key = _($.ui.keyCode).chain() + .map(function (v, k) { return {name: k, code: v}; }) + .find(function (o) { return o.code === e.which; }) + .value(); + if (!key) { return; } + var method = e.type + '_' + key.name; + if (!(method in self)) { return; } + self[method](e); }); }, /** - * Saves the current row, and returns a Deferred resolving to an object - * with the following properties: + * Saves the current record, and goes to the next one (creation or + * edition) * - * ``created`` - * Boolean flag indicating whether the record saved was being created - * (``true`` or edited (``false``) - * ``edited_record`` - * The result of saving the record (either the newly created record, - * or the post-edition record), after insertion in the Collection if - * needs be. - * - * @returns {$.Deferred<{created: Boolean, edited_record: Record}>} + * @private + * @param {String} [next_record='succ'] method to call on the records collection to get the next record to edit + * @param {Object} [options] + * @param {String} [options.focus_field] + * @return {*} */ - save_row: function () { - //noinspection JSPotentiallyInvalidConstructorUsage - var self = this, done = $.Deferred(); - return this.edition_form - .do_save(null, this.options.editable === 'top') - .pipe(function (result) { - if (result.created && !self.edition_id) { - self.records.add({id: result.result}, - {at: self.options.editable === 'top' ? 0 : null}); - self.edition_id = result.result; - } - var edited_record = self.records.get(self.edition_id); - - return $.when( - self.handle_onwrite(self.edition_id), - self.cancel_pending_edition().then(function () { - $(self).trigger('saved', [self.dataset]); - })).pipe(function () { - return { - created: result.created || false, - edited_record: edited_record - }; - }, null); - }, null); + _next: function (next_record, options) { + next_record = next_record || 'succ'; + var self = this; + return this.save_edition().pipe(function (saveInfo) { + if (saveInfo.created) { + return self.start_edition(); + } + var record = self.records[next_record]( + saveInfo.record, {wraparound: true}); + return self.start_edition(record, options); + }); + }, + keyup_ENTER: function () { + return this._next(); + }, + keyup_ESCAPE: function () { + return this.cancel_edition(); }, /** - * If the current list is being edited, ensures it's saved + * Gets the selection range (start, end) for the provided element, + * returns ``null`` if it can't get a range. + * + * @private */ - ensure_saved: function () { - if (this.edition) { - return this.save_row(); + _text_selection_range: function (el) { + var selectionStart; + try { + selectionStart = el.selectionStart; + } catch (e) { + // radio or checkbox throw on selectionStart access + return null; + } + if (selectionStart !== undefined) { + return { + start: selectionStart, + end: el.selectionEnd + }; + } else if (document.body.createTextRange) { + throw new Error("Implement text range handling for MSIE"); + var sel = document.body.createTextRange(); + if (sel.parentElement() === el) { + + } } - //noinspection JSPotentiallyInvalidConstructorUsage - return $.Deferred().resolve().promise(); + // Element without selection ranges (select, div/@contenteditable) + return null; + }, + _text_cursor: function (el) { + var selection = this._text_selection_range(el); + if (!selection) { + return null; + } + if (selection.start !== selection.end) { + return {position: null, collapsed: false}; + } + return {position: selection.start, collapsed: true}; + }, + /** + * Checks if the cursor is at the start of the provided el + * + * @param {HTMLInputElement | HTMLTextAreaElement} + * @returns {Boolean} + * @private + */ + _at_start: function (cursor, el) { + return cursor.collapsed && (cursor.position === 0); }, /** - * Cancels the edition of the row for the current dataset index + * Checks if the cursor is at the end of the provided el + * + * @param {HTMLInputElement | HTMLTextAreaElement} + * @returns {Boolean} + * @private */ - cancel_edition: function () { - this.cancel_pending_edition(); + _at_end: function (cursor, el) { + return cursor.collapsed && (cursor.position === el.value.length); }, /** - * Edits record currently selected via dataset + * @param DOMEvent event + * @param {String} record_direction direction to move into to get the next record (pred | succ) + * @param {Function} is_valid_move whether the edition should be moved to the next record + * @private */ - edit_record: function (record_id) { - this.render_row_as_form( - this.$current.find('[data-id=' + record_id + ']')); - $(this).trigger( - 'edit', - [record_id, this.dataset]); - }, - new_record: function () { - this.dataset.index = null; - this.render_row_as_form(); - }, - render_record: function (record) { - var index = this.records.indexOf(record), - self = this; - // FIXME: context dict should probably be extracted cleanly - return QWeb.render('ListView.row', { - columns: this.columns, - options: this.options, - record: record, - row_parity: (index % 2 === 0) ? 'even' : 'odd', - view: this.view, - render_cell: function () { - return self.render_cell.apply(self, arguments); }, - edited: !!this.edition_form + _key_move_record: function (event, record_direction, is_valid_move) { + if (!this.editor.is_editing('edit')) { return $.when(); } + var cursor = this._text_cursor(event.target); + // if text-based input (has a cursor) + // and selecting (not collapsed) or not at a field boundary + // don't move to the next record + if (cursor && !is_valid_move(event.target, cursor)) { return $.when(); } + + event.preventDefault(); + var source_field = $(event.target).closest('[data-fieldname]') + .attr('data-fieldname'); + return this._next(record_direction, {focus_field: source_field}); + + }, + keydown_UP: function (e) { + var self = this; + return this._key_move_record(e, 'pred', function (el, cursor) { + return self._at_start(cursor, el); }); + }, + keydown_DOWN: function (e) { + var self = this; + return this._key_move_record(e, 'succ', function (el, cursor) { + return self._at_end(cursor, el); + }); + }, + + keydown_LEFT: function (e) { + // If the cursor is at the beginning of the field + var source_field = $(e.target).closest('[data-fieldname]') + .attr('data-fieldname'); + var cursor = this._text_cursor(e.target); + if (cursor && !this._at_start(cursor, e.target)) { return $.when(); } + + var fields_order = this.editor.form.fields_order; + var field_index = _(fields_order).indexOf(source_field); + + // Look for the closest visible form field to the left + var fields = this.editor.form.fields; + var field; + do { + if (--field_index < 0) { return $.when(); } + + field = fields[fields_order[field_index]]; + } while (!field.$el.is(':visible')); + + // and focus it + field.focus(); + return $.when(); + }, + keydown_RIGHT: function (e) { + // same as above, but with cursor at the end of the field and + // looking for new fields at the right + var source_field = $(e.target).closest('[data-fieldname]') + .attr('data-fieldname'); + var cursor = this._text_cursor(e.target); + if (cursor && !this._at_end(cursor, e.target)) { return $.when(); } + + var fields_order = this.editor.form.fields_order; + var field_index = _(fields_order).indexOf(source_field); + + var fields = this.editor.form.fields; + var field; + do { + if (++field_index >= fields_order.length) { return $.when(); } + + field = fields[fields_order[field_index]]; + } while (!field.$el.is(':visible')); + + field.focus(); + return $.when(); + }, + keydown_TAB: function (e) { + var form = this.editor.form; + var last_field = _(form.fields_order).chain() + .map(function (name) { return form.fields[name]; }) + .filter(function (field) { return field.$el.is(':visible'); }) + .last() + .value(); + // tabbed from last field in form + if (last_field && last_field.$el.has(e.target).length) { + e.preventDefault(); + return this._next(); + } + return $.when(); } }); - - instance.web.ListEditableFormView = instance.web.FormView.extend({ - init: function() { - this._super.apply(this, arguments); - this.rendering_engine = new instance.web.ListEditableRenderingEngine(this); - this.options.initial_mode = "edit"; + + instance.web.list.Editor = instance.web.Widget.extend({ + /** + * @constructs instance.web.list.Editor + * @extends instance.web.Widget + * + * Adapter between listview and formview for editable-listview purposes + * + * @param {instance.web.Widget} parent + * @param {Object} options + * @param {instance.web.FormView} [options.formView=instance.web.FormView] + * @param {Object} [options.delegate] + */ + init: function (parent, options) { + this._super(parent); + this.options = options || {}; + _.defaults(this.options, { + formView: instance.web.FormView, + delegate: this.getParent() + }); + this.delegate = this.options.delegate; + + this.record = null; + + this.form = new (this.options.formView)( + this, this.delegate.dataset, false, { + initial_mode: 'edit', + disable_autofocus: true, + $buttons: $(), + $pager: $() + }); }, - renderElement: function() {} - }); - - instance.web.ListEditableRenderingEngine = instance.web.form.FormRenderingEngineInterface.extend({ - init: function(view) { - this.view = view; + start: function () { + var self = this; + var _super = this._super(); + this.form.embedded_view = this._validate_view( + this.delegate.edition_view(this)); + var form_ready = this.form.appendTo(this.$el).then( + self.form.proxy('do_hide')); + return $.when(_super, form_ready); }, - set_fields_view: function(fields_view) { - this.fvg = fields_view; + _validate_view: function (edition_view) { + if (!edition_view) { + throw new Error("editor delegate's #edition_view must return " + + "a view descriptor"); + } + var arch = edition_view.arch; + if (!(arch && arch.children instanceof Array)) { + throw new Error("Editor delegate's #edition_view must have a" + + " non-empty arch") + } + if (!(arch.tag === "form")) { + throw new Error("Editor delegate's #edition_view must have a" + + " 'form' root node"); + } + if (!(arch.attrs && arch.attrs.version === "7.0")) { + throw new Error("Editor delegate's #edition_view must be a" + + " version 7 view"); + } + if (!/\boe_form_container\b/.test(arch.attrs['class'])) { + throw new Error("Editor delegate's #edition_view must have the" + + " class 'oe_form_container' on its root" + + " element"); + } + + return edition_view; }, - set_tags_registry: function(tags_registry) { - this.tags_registry = tags_registry; + + /** + * + * @param {String} [state] either ``new`` or ``edit`` + * @return {Boolean} + */ + is_editing: function (state) { + if (!this.record) { + return false; + } + switch(state) { + case null: case undefined: + return true; + case 'new': return !this.record.id; + case 'edit': return !!this.record.id; + } + throw new Error("is_editing's state filter must be either `new` or" + + " `edit` if provided"); }, - set_fields_registry: function(fields_registry) { - this.fields_registry = fields_registry; + _focus_setup: function (focus_field) { + var form = this.form; + + var field; + // If a field to focus was specified + if (focus_field + // Is actually in the form + && (field = form.fields[focus_field]) + // And is visible + && field.$el.is(':visible')) { + // focus it + field.focus(); + return; + } + + _(form.fields_order).detect(function (name) { + // look for first visible field in fields_order, focus it + var field = form.fields[name]; + if (!field.$el.is(':visible')) { + return false; + } + // Stop as soon as a field got focused + return field.focus() !== false; + }); }, - render_to: function($element) { + edit: function (record, configureField, options) { + // TODO: specify sequence of edit calls var self = this; - - var xml = instance.web.json_node_to_xml(this.fvg.arch); - var $xml = $(xml); - - if (this.view.editable_list.options.selectable) - $("<td>").appendTo($element); - - $xml.children().each(function(i, el) { - var modifiers = JSON.parse($(el).attr("modifiers") || "{}"); - var $td = $("<td>"); - if (modifiers.tree_invisible === true) - $td.hide(); - var tag_name = el.tagName.toLowerCase(); - var w; - if (tag_name === "field") { - var name = $(el).attr("name"); - var key = $(el).attr('widget') || self.fvg.fields[name].type; - var obj = self.view.fields_registry.get_object(key); - w = new (obj)(self.view, instance.web.xml_to_json(el)); - self.view.register_field(w, $(el).attr("name")); - } else { - var obj = self.tags_registry.get_object(tag_name); - w = new (obj)(self.view, instance.web.xml_to_json(el)); - } - w.appendTo($td); - $td.appendTo($element); + var form = self.form; + var loaded = record + ? form.on_record_loaded(_.extend({}, record)) + : form.load_defaults(); + + return loaded.pipe(function () { + return form.do_show({reload: false}); + }).pipe(function () { + self.record = form.datarecord; + _(form.fields).each(function (field, name) { + configureField(name, field); + }); + self._focus_setup(options && options.focus_field); + return form; }); - save = QWeb.render('ListView.row.save'); - $(save).appendTo($element); }, + save: function () { + var self = this; + return this.form + .do_save(this.delegate.prepends_on_create()) + .pipe(function (result) { + var created = result.created && !self.record.id; + if (created) { + self.record.id = result.result; + } + return self.cancel(); + }); + }, + cancel: function (force) { + if (!(force || this.form.can_be_discarded())) { + return $.Deferred().reject({ + message: "The form's data can not be discarded"}).promise(); + } + var record = this.record; + this.record = null; + this.form.do_hide(); + return $.when(record); + } + }); + + instance.web.ListView.Groups.include(/** @lends instance.web.ListView.Groups# */{ + passtrough_events: instance.web.ListView.Groups.prototype.passtrough_events + " edit saved", + get_row_for: function (record) { + return _(this.children).chain() + .invoke('get_row_for', record) + .compact() + .first() + .value(); + } + }); + + instance.web.ListView.List.include(/** @lends instance.web.ListView.List# */{ + row_clicked: function (event) { + if (!this.view.editable() || ! this.view.is_action_enabled('edit')) { + return this._super.apply(this, arguments); + } + var record_id = $(event.currentTarget).data('id'); + this.view.start_edition( + record_id ? this.records.get(record_id) : null, { + focus_field: $(event.target).data('field') + }); + }, + /** + * If a row mapping to the record (@data-id matching the record's id or + * no @data-id if the record has no id), returns it. Otherwise returns + * ``null``. + * + * @param {Record} record the record to get a row for + * @return {jQuery|null} + */ + get_row_for: function (record) { + var id; + var $row = this.$current.children('[data-id=' + record.get('id') + ']'); + if ($row.length) { + return $row; + } + return null; + } }); }; diff --git a/addons/web/static/src/js/view_tree.js b/addons/web/static/src/js/view_tree.js index 9e938fe293a..e0493a446e2 100644 --- a/addons/web/static/src/js/view_tree.js +++ b/addons/web/static/src/js/view_tree.js @@ -63,6 +63,12 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie } return fields; }, + store_record:function(records){ + var self = this; + _(records).each(function (record) { + self.records[record.id] = record; + }); + }, on_loaded: function (fields_view) { var self = this; var has_toolbar = !!fields_view.arch.attrs.toolbar; @@ -78,25 +84,28 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie }); this.fields = fields_view.fields; this.hook_row_click(); - this.$element.html(QWeb.render('TreeView', { + this.$el.html(QWeb.render('TreeView', { 'title': this.fields_view.arch.attrs.string, 'fields_view': this.fields_view.arch.children, 'fields': this.fields, 'toolbar': has_toolbar })); + this.$el.addClass(this.fields_view.arch.attrs['class']); this.dataset.read_slice(this.fields_list()).then(function(records) { + self.store_record(records); if (!has_toolbar) { // WARNING: will do a second read on the same ids, but only on // first load so not very important - self.getdata(null, _(records).pluck('id')); + self.render_data({'null':records}) + self.getdata(_.pluck(records,"id")); return; } - var $select = self.$element.find('select') + var $select = self.$el.find('select') .change(function () { var $option = $(this).find(':selected'); - self.getdata($option.val(), $option.data('children')); + self.getdata($option.val()); }); _(records).each(function (record) { self.records[record.id] = record; @@ -111,7 +120,12 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie $select.change(); } }); - + this.$el.find("#tree_view_expand").click(function(){ + self.expand_all(); + }); + this.$el.find("#tree_view_collapse").click(function(){ + self.collpase_all(); + }); // TODO store open nodes in url ?... this.do_push_state({}); @@ -127,6 +141,22 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie return [color, py.parse(py.tokenize(expr)), expr]; }).value(); }, + expand_all: function(){ + var self = this; + var tr = this.$el.find(".oe-treeview-table tbody tr[id^='treerow_']"); + _.each(tr,function(rec){ + self.showcontent($(rec).attr('data-id'),true); + }); + }, + collpase_all: function(){ + var self = this; + var root_tr = this.$el.find(".oe-treeview-table tbody tr[data-level='"+1+"']"); + _.each(root_tr,function(rec){ + if($(rec).hasClass('oe_open')){ + self.showcontent($(rec).attr('data-id'),false); + } + }); + }, /** * Returns the color for the provided record in the current view (from the * ``@colors`` attribute) @@ -157,65 +187,61 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie */ hook_row_click: function () { var self = this; - this.$element.delegate('.treeview-td span, .treeview-tr span', 'click', function (e) { + this.$el.delegate('.treeview-td span, .treeview-tr span', 'click', function (e) { e.stopImmediatePropagation(); self.activate($(this).closest('tr').data('id')); }); - this.$element.delegate('.treeview-tr', 'click', function () { - var is_loaded = 0, - $this = $(this), + this.$el.delegate('.treeview-tr', 'click', function () { + var $this = $(this), record_id = $this.data('id'), - record = self.records[record_id], - children_ids = record[self.children_field]; - - _(children_ids).each(function(childid) { - if (self.$element.find('#treerow_' + childid).length) { - if (self.$element.find('#treerow_' + childid).is(':hidden')) { - is_loaded = -1; - } else { - is_loaded++; - } - } - }); - if (is_loaded === 0) { - if (!$this.parent().hasClass('oe-open')) { - self.getdata(record_id, children_ids); - } - } else { - self.showcontent(record_id, is_loaded < 0); - } + bool = $this.parent().hasClass('oe_open'); + self.showcontent(record_id, !bool); }); }, // get child data of selected value - getdata: function (id, children_ids) { + getdata: function (id) { var self = this; - - self.dataset.read_ids(children_ids, this.fields_list()).then(function(records) { - _(records).each(function (record) { - self.records[record.id] = record; - }); - - var $curr_node = self.$element.find('#treerow_' + id); + var parent_child ={}; + id = _.isArray(id)?id:parseInt(id); + var ir_model_data = new instance.web.Model(this.model,self.dataset.get_context() || {},[['id','child_of',id]]).query(); + ir_model_data._execute().then(function(records){ + self.store_record(records); + _.each(records,function(rec){ + if(rec[self.children_field].length === 0)return; + parent_child[rec.id] = []; + _.each(rec[self.children_field],function(key){ + parent_child[rec.id].push(self.records[key]); + }); + }) + self.render_data(parent_child); + }); + }, + render_data: function(groupby){ + var self = this; + _.each(_.keys(groupby),function(key){ + var $curr_node = self.$el.find('#treerow_' + key); + var record = groupby[key]; var children_rows = QWeb.render('TreeView.rows', { - 'records': records, + 'records': record, 'children_field': self.children_field, 'fields_view': self.fields_view.arch.children, 'fields': self.fields, - 'level': $curr_node.data('level') || 0, + 'level': ($curr_node.data('level') || 0) + 1, 'render': instance.web.format_value, 'color_for': self.color_for }); - if ($curr_node.length) { - $curr_node.addClass('oe-open'); + $curr_node.addClass('oe_open'); $curr_node.after(children_rows); } else { - self.$element.find('tbody').html(children_rows); + self.$el.find('tbody').html(children_rows); } }); + self.collpase_all(); }, + // Get details in listview activate: function(id) { var self = this; @@ -246,25 +272,17 @@ instance.web.TreeView = instance.web.View.extend(/** @lends instance.web.TreeVie // show & hide the contents showcontent: function (record_id, show) { - this.$element.find('#treerow_' + record_id) - .toggleClass('oe-open', show); + this.$el.find('#treerow_' + record_id) + .toggleClass('oe_open', show); _(this.records[record_id][this.children_field]).each(function (child_id) { - var $child_row = this.$element.find('#treerow_' + child_id); - if ($child_row.hasClass('oe-open')) { + var $child_row = this.$el.find('#treerow_' + child_id); + if ($child_row.hasClass('oe_open')) { this.showcontent(child_id, false); } $child_row.toggle(show); }, this); }, - do_show: function () { - this.$element.show(); - }, - - do_hide: function () { - this.$element.hide(); - this.hidden = true; - } }); }; diff --git a/addons/web/static/src/js/views.js b/addons/web/static/src/js/views.js index 753e14fa251..9e7db823bc0 100644 --- a/addons/web/static/src/js/views.js +++ b/addons/web/static/src/js/views.js @@ -10,51 +10,189 @@ instance.web.ActionManager = instance.web.Widget.extend({ init: function(parent) { this._super(parent); this.inner_action = null; - this.inner_viewmanager = null; + this.inner_widget = null; this.dialog = null; - this.dialog_viewmanager = null; - this.client_widget = null; + this.dialog_widget = null; + this.breadcrumbs = []; + this.on('history_back', this, function() { + return this.history_back(); + }); + }, + start: function() { + this._super.apply(this, arguments); + this.$el.on('click', 'a.oe_breadcrumb_item', this.on_breadcrumb_clicked); }, dialog_stop: function () { if (this.dialog) { - this.dialog_viewmanager.destroy(); - this.dialog_viewmanager = null; + this.dialog_widget.destroy(); + this.dialog_widget = null; this.dialog.destroy(); this.dialog = null; } }, - content_stop: function () { - if (this.inner_viewmanager) { - this.inner_viewmanager.destroy(); - this.inner_viewmanager = null; + /** + * Add a new item to the breadcrumb + * + * If the title of an item is an array, the multiple title mode is in use. + * (eg: a widget with multiple views might need to display a title for each view) + * In multiple title mode, the show() callback can check the index it receives + * in order to detect which of its titles has been clicked on by the user. + * + * @param {Object} item breadcrumb item + * @param {Object} item.widget widget containing the view(s) to be added to the breadcrumb added + * @param {Function} [item.show] triggered whenever the widget should be shown back + * @param {Function} [item.hide] triggered whenever the widget should be shown hidden + * @param {Function} [item.destroy] triggered whenever the widget should be destroyed + * @param {String|Array} [item.title] title(s) of the view(s) to be displayed in the breadcrumb + * @param {Function} [item.get_title] should return the title(s) of the view(s) to be displayed in the breadcrumb + */ + push_breadcrumb: function(item) { + var last = this.breadcrumbs.slice(-1)[0]; + if (last) { + last.hide(); + } + var item = _.extend({ + show: function(index) { + this.widget.$el.show(); + }, + hide: function() { + this.widget.$el.hide(); + }, + destroy: function() { + this.widget.destroy(); + }, + get_title: function() { + return this.title || this.widget.get('title'); + } + }, item); + item.id = _.uniqueId('breadcrumb_'); + this.breadcrumbs.push(item); + }, + history_back: function() { + var last = this.breadcrumbs.slice(-1)[0]; + if (!last) { + return false; + } + var title = last.get_title(); + if (_.isArray(title) && title.length > 1) { + return this.select_breadcrumb(this.breadcrumbs.length - 1, title.length - 2); + } else if (this.breadcrumbs.length === 1) { + // Only one single titled item in breadcrumb, most of the time you want to trigger back to home + return false; + } else { + var prev = this.breadcrumbs[this.breadcrumbs.length - 2]; + title = prev.get_title(); + return this.select_breadcrumb(this.breadcrumbs.length - 2, _.isArray(title) ? title.length - 1 : undefined); + } + }, + on_breadcrumb_clicked: function(ev) { + var $e = $(ev.target); + var id = $e.data('id'); + var index; + for (var i = this.breadcrumbs.length - 1; i >= 0; i--) { + if (this.breadcrumbs[i].id == id) { + index = i; + break; + } + } + var subindex = $e.parent().find('a.oe_breadcrumb_item[data-id=' + $e.data('id') + ']').index($e); + this.select_breadcrumb(index, subindex); + }, + select_breadcrumb: function(index, subindex) { + for (var i = this.breadcrumbs.length - 1; i >= 0; i--) { + if (i > index) { + if (this.remove_breadcrumb(i) === false) { + return false; + } + } + } + var item = this.breadcrumbs[index]; + item.show(subindex); + this.inner_widget = item.widget; + return true; + }, + clear_breadcrumbs: function() { + for (var i = this.breadcrumbs.length - 1; i >= 0; i--) { + if (this.remove_breadcrumb(0) === false) { + break; + } + } + }, + remove_breadcrumb: function(index) { + var item = this.breadcrumbs.splice(index, 1)[0]; + if (item) { + var dups = _.filter(this.breadcrumbs, function(it) { + return item.widget === it.widget; + }); + if (!dups.length) { + if (this.getParent().has_uncommitted_changes()) { + this.inner_widget = item.widget; + this.breadcrumbs.splice(index, 0, item); + return false; + } else { + item.destroy(); + } + } } - if (this.client_widget) { - this.client_widget.destroy(); - this.client_widget = null; + var last_widget = this.breadcrumbs.slice(-1)[0]; + this.inner_widget = last_widget && last_widget.widget; + }, + get_title: function() { + var titles = []; + for (var i = 0; i < this.breadcrumbs.length; i += 1) { + var item = this.breadcrumbs[i]; + var tit = item.get_title(); + if (!_.isArray(tit)) { + tit = [tit]; + } + for (var j = 0; j < tit.length; j += 1) { + var label = _.escape(tit[j]); + if (i === this.breadcrumbs.length - 1 && j === tit.length - 1) { + titles.push(_.str.sprintf('<span class="oe_breadcrumb_item">%s</span>', label)); + } else { + titles.push(_.str.sprintf('<a href="#" class="oe_breadcrumb_item" data-id="%s">%s</a>', item.id, label)); + } + } } + return titles.join(' <span class="oe_fade">/</span> '); }, do_push_state: function(state) { + state = state || {}; if (this.getParent() && this.getParent().do_push_state) { if (this.inner_action) { - state['model'] = this.inner_action.res_model; + state['title'] = this.inner_action.name; + if(this.inner_action.type == 'ir.actions.act_window') { + state['model'] = this.inner_action.res_model; + } if (this.inner_action.id) { - state['action_id'] = this.inner_action.id; + state['action'] = this.inner_action.id; + } else if (this.inner_action.type == 'ir.actions.client') { + state['action'] = this.inner_action.tag; + //state = _.extend(this.inner_action.params || {}, state); } } - this.getParent().do_push_state(state); + if(!this.dialog) { + this.getParent().do_push_state(state); + } } }, do_load_state: function(state, warm) { var self = this, action_loaded; - if (state.action_id) { - var run_action = (!this.inner_viewmanager) || this.inner_viewmanager.action.id !== state.action_id; - if (run_action) { + if (state.action) { + if (_.isString(state.action) && instance.web.client_actions.contains(state.action)) { + var action_client = {type: "ir.actions.client", tag: state.action, params: state}; this.null_action(); - action_loaded = this.do_action(state.action_id); - instance.webclient.menu.has_been_loaded.then(function() { - instance.webclient.menu.open_action(state.action_id); - }); + action_loaded = this.do_action(action_client); + } else { + var run_action = (!this.inner_widget || !this.inner_widget.action) || this.inner_widget.action.id !== state.action; + if (run_action) { + this.null_action(); + action_loaded = this.do_action(state.action); + instance.webclient.menu.has_been_loaded.then(function() { + instance.webclient.menu.open_action(state.action); + }); + } } } else if (state.model && state.id) { // TODO handle context & domain ? @@ -68,38 +206,37 @@ instance.web.ActionManager = instance.web.Widget.extend({ action_loaded = this.do_action(action); } else if (state.sa) { // load session action - var self = this; this.null_action(); action_loaded = this.rpc('/web/session/get_session_action', {key: state.sa}).pipe(function(action) { if (action) { return self.do_action(action); } }); - } else if (state.client_action) { - this.null_action(); - this.ir_actions_client(state.client_action); } $.when(action_loaded || null).then(function() { - if (self.inner_viewmanager) { - self.inner_viewmanager.do_load_state(state, warm); + if (self.inner_widget && self.inner_widget.do_load_state) { + self.inner_widget.do_load_state(state, warm); } }); }, - do_action: function(action, on_close) { - if (_.isNumber(action)) { + do_action: function(action, on_close, clear_breadcrumbs) { + if (_.isString(action) && instance.web.client_actions.contains(action)) { + var action_client = { type: "ir.actions.client", tag: action }; + return this.do_action(action_client, on_close, clear_breadcrumbs); + } else if (_.isNumber(action) || _.isString(action)) { var self = this; - return self.rpc("/web/action/load", { action_id: action }, function(result) { - self.do_action(result.result, on_close); + return self.rpc("/web/action/load", { action_id: action }).pipe(function(result) { + return self.do_action(result.result, on_close, clear_breadcrumbs); }); } if (!action.type) { console.error("No type for action", action); - return; + return $.Deferred().reject(); } var type = action.type.replace(/\./g,'_'); var popup = action.target === 'new'; - var inline = action.target === 'inline'; + var inline = action.target === 'inline' || action.target === 'inlineview'; action.flags = _.extend({ views_switcher : !popup && !inline, search_view : !popup && !inline, @@ -110,47 +247,79 @@ instance.web.ActionManager = instance.web.Widget.extend({ }, action.flags || {}); if (!(type in this)) { console.error("Action manager can't handle action of type " + action.type, action); - return; + return $.Deferred().reject(); } - return this[type](action, on_close); + return this[type](action, on_close, clear_breadcrumbs); }, null_action: function() { this.dialog_stop(); - this.content_stop(); - }, - ir_actions_act_window: function (action, on_close) { - var self = this; - if (_(['base.module.upgrade', 'base.setup.installer']) - .contains(action.res_model)) { - var old_close = on_close; - on_close = function () { - instance.webclient.do_reload().then(old_close); + this.clear_breadcrumbs(); + }, + ir_actions_common: function(action, on_close, clear_breadcrumbs) { + var self = this, klass, widget, post_process; + if (this.inner_widget && (action.type === 'ir.actions.client' || action.target !== 'new')) { + if (this.getParent().has_uncommitted_changes()) { + return $.Deferred().reject(); + } else if (clear_breadcrumbs) { + this.clear_breadcrumbs(); + } + } + if (action.type === 'ir.actions.client') { + var ClientWidget = instance.web.client_actions.get_object(action.tag); + widget = new ClientWidget(this, action.params); + klass = 'oe_act_client'; + post_process = function() { + self.push_breadcrumb({ + widget: widget, + title: action.name + }); + if (action.tag !== 'reload') { + self.do_push_state({}); + } }; + } else { + widget = new instance.web.ViewManagerAction(this, action); + klass = 'oe_act_window'; + post_process = widget.proxy('add_breadcrumb'); } if (action.target === 'new') { - if (this.dialog == null) { - this.dialog = new instance.web.Dialog(this, { width: '80%' }); + if (this.dialog === null) { + // These buttons will be overwrited by <footer> if any + this.dialog = new instance.web.Dialog(this, { + buttons: { "Close": function() { $(this).dialog("close"); }}, + dialogClass: klass + }); if(on_close) this.dialog.on_close.add(on_close); } else { - this.dialog_viewmanager.destroy(); + this.dialog_widget.destroy(); } this.dialog.dialog_title = action.name; - this.dialog_viewmanager = new instance.web.ViewManagerAction(this, action); - this.dialog_viewmanager.appendTo(this.dialog.$element); + this.dialog_widget = widget; + this.dialog_widget.appendTo(this.dialog.$el); this.dialog.open(); } else { this.dialog_stop(); - this.content_stop(); + this.inner_action = action; + this.inner_widget = widget; + post_process(); + this.inner_widget.appendTo(this.$el); + } + }, + ir_actions_act_window: function (action, on_close, clear_breadcrumbs) { + var self = this; + if (action.target !== 'new') { if(action.menu_id) { + this.dialog_stop(); return this.getParent().do_action(action, function () { instance.webclient.menu.open_menu(action.menu_id); - }); + }, clear_breadcrumbs); } - this.inner_action = action; - this.inner_viewmanager = new instance.web.ViewManagerAction(this, action); - this.inner_viewmanager.appendTo(this.$element); } + return this.ir_actions_common(action, on_close, clear_breadcrumbs); + }, + ir_actions_client: function (action, on_close, clear_breadcrumbs) { + return this.ir_actions_common(action, on_close, clear_breadcrumbs); }, ir_actions_act_window_close: function (action, on_closed) { if (!this.dialog && on_closed) { @@ -158,24 +327,18 @@ instance.web.ActionManager = instance.web.Widget.extend({ } this.dialog_stop(); }, - ir_actions_server: function (action, on_closed) { + ir_actions_server: function (action, on_closed, clear_breadcrumbs) { var self = this; this.rpc('/web/action/run', { action_id: action.id, context: action.context || {} }).then(function (action) { - self.do_action(action, on_closed) + self.do_action(action, on_closed, clear_breadcrumbs) }); }, - ir_actions_client: function (action) { - this.content_stop(); - this.dialog_stop(); - var ClientWidget = instance.web.client_actions.get_object(action.tag); - (this.client_widget = new ClientWidget(this, action.params)).appendTo(this.$element); - }, ir_actions_report_xml: function(action, on_closed) { var self = this; - $.blockUI(); + instance.web.blockUI(); self.rpc("/web/session/eval_domain_and_context", { contexts: [action.context], domains: [] @@ -185,7 +348,7 @@ instance.web.ActionManager = instance.web.Widget.extend({ self.session.get_file({ url: '/web/report', data: {action: JSON.stringify(action)}, - complete: $.unblockUI, + complete: instance.web.unblockUI, success: function(){ if (!self.dialog && on_closed) { on_closed(); @@ -199,9 +362,6 @@ instance.web.ActionManager = instance.web.Widget.extend({ ir_actions_act_url: function (action) { window.open(action.url, action.target === 'self' ? '_self' : '_blank'); }, - ir_ui_menu: function (action) { - this.getParent().do_action(action); - } }); instance.web.ViewManager = instance.web.Widget.extend({ @@ -235,7 +395,7 @@ instance.web.ViewManager = instance.web.Widget.extend({ start: function() { this._super(); var self = this; - this.$element.find('.oe_view_manager_switch a').click(function() { + this.$el.find('.oe_view_manager_switch a').click(function() { self.on_mode_switch($(this).data('view-type')); }).tipsy(); var views_ids = {}; @@ -244,9 +404,9 @@ instance.web.ViewManager = instance.web.Widget.extend({ deferred : $.Deferred(), controller : null, options : _.extend({ - $buttons : self.$element.find('.oe_view_manager_buttons'), - $sidebar : self.flags.sidebar ? self.$element.find('.oe_view_manager_sidebar') : undefined, - $pager : self.$element.find('.oe_view_manager_pager'), + $buttons : self.$el.find('.oe_view_manager_buttons'), + $sidebar : self.flags.sidebar ? self.$el.find('.oe_view_manager_sidebar') : undefined, + $pager : self.$el.find('.oe_view_manager_pager'), action : self.action, action_views_ids : views_ids }, self.flags, self.flags[view.view_type] || {}, view.options || {}) @@ -254,7 +414,7 @@ instance.web.ViewManager = instance.web.Widget.extend({ views_ids[view.view_type] = view.view_id; }); if (this.flags.views_switcher === false) { - this.$element.find('.oe_view_manager_switch').hide(); + this.$el.find('.oe_view_manager_switch').hide(); } // If no default view defined, switch to the first one in sequence var default_view = this.flags.default_view || this.views_src[0].view_type; @@ -271,8 +431,10 @@ instance.web.ViewManager = instance.web.Widget.extend({ var self = this; var view = this.views[view_type]; var view_promise; - if(!view) + var form = this.views['form']; + if (!view || (form && form.controller && !form.controller.can_be_discarded())) { return $.Deferred().reject(); + } if (!no_store) { this.views_history.push(view_type); @@ -280,35 +442,7 @@ instance.web.ViewManager = instance.web.Widget.extend({ this.active_view = view_type; if (!view.controller) { - // Lazy loading of views - var controllerclass = this.registry.get_object(view_type); - var options = _.clone(view.options); - if (view_type === "form" && this.action) { - switch (this.action.target) { - case 'new': - case 'inline': - options.initial_mode = 'edit'; - break; - } - } - var controller = new controllerclass(this, this.dataset, view.view_id, options); - if (view.embedded_view) { - controller.set_embedded_view(view.embedded_view); - } - controller.do_switch_view.add_last(_.bind(this.switch_view, this)); - controller.do_prev_view.add_last(this.on_prev_view); - var container = this.$element.find(".oe_view_manager_view_" + view_type); - view_promise = controller.appendTo(container); - this.views[view_type].controller = controller; - this.views[view_type].deferred.resolve(view_type); - $.when(view_promise).then(function() { - self.on_controller_inited(view_type, controller); - if (self.searchview - && self.flags.auto_search - && view.controller.searchable !== false) { - self.searchview.ready.then(self.searchview.do_search); - } - }); + view_promise = this.do_create_view(view_type); } else if (this.searchview && self.flags.auto_search && view.controller.searchable !== false) { @@ -319,28 +453,128 @@ instance.web.ViewManager = instance.web.Widget.extend({ this.searchview[(view.controller.searchable === false || this.searchview.hidden) ? 'hide' : 'show'](); } - this.$element - .find('.oe_view_manager_switch a').parent().removeClass('active') - this.$element + this.$el + .find('.oe_view_manager_switch a').parent().removeClass('active'); + this.$el .find('.oe_view_manager_switch a').filter('[data-view-type="' + view_type + '"]') .parent().addClass('active'); - $.when(view_promise).then(function () { + return $.when(view_promise).then(function () { _.each(_.keys(self.views), function(view_name) { var controller = self.views[view_name].controller; if (controller) { + var container = self.$el.find(".oe_view_manager_view_" + view_name + ":first"); if (view_name === view_type) { + container.show(); controller.do_show(view_options || {}); } else { + container.hide(); controller.do_hide(); } + // put the <footer> in the dialog's buttonpane + if (self.$el.parent('.ui-dialog-content') && self.$el.find('footer')) { + self.$el.parent('.ui-dialog-content').parent().find('div.ui-dialog-buttonset').hide() + self.$el.find('footer').appendTo( + self.$el.parent('.ui-dialog-content').parent().find('div.ui-dialog-buttonpane') + ); + } } }); + }); + }, + do_create_view: function(view_type) { + // Lazy loading of views + var self = this; + var view = this.views[view_type]; + var viewclass = this.registry.get_object(view_type); + var options = _.clone(view.options); + if (view_type === "form" && this.action && (this.action.target == 'new' || this.action.target == 'inline')) { + options.initial_mode = 'edit'; + } + var controller = new viewclass(this, this.dataset, view.view_id, options); + + controller.on('history_back', this, function() { + var am = self.getParent(); + if (am && am.trigger) { + return am.trigger('history_back'); + } + }); - self.$element.find('.oe_view_title_text:first').text( - self.display_title()); + controller.on("change:title", this, function() { + if (self.active_view === view_type) { + self.set_title(controller.get('title')); + } + }); + + if (view.embedded_view) { + controller.set_embedded_view(view.embedded_view); + } + controller.do_switch_view.add_last(_.bind(this.switch_view, this)); + + controller.do_prev_view.add_last(this.on_prev_view); + var container = this.$el.find(".oe_view_manager_view_" + view_type); + var view_promise = controller.appendTo(container); + this.views[view_type].controller = controller; + this.views[view_type].deferred.resolve(view_type); + return $.when(view_promise).then(function() { + self.on_controller_inited(view_type, controller); + if (self.searchview + && self.flags.auto_search + && view.controller.searchable !== false) { + self.searchview.ready.then(self.searchview.do_search); + } + }); + }, + set_title: function(title) { + this.$el.find('.oe_view_title_text:first').text(title); + }, + add_breadcrumb: function() { + var self = this; + var views = [this.active_view || this.views_src[0].view_type]; + this.on_mode_switch.add(function(mode) { + var last = views.slice(-1)[0]; + if (mode !== last) { + if (mode !== 'form') { + views.length = 0; + } + views.push(mode); + } + }); + this.getParent().push_breadcrumb({ + widget: this, + action: this.action, + show: function(index) { + var view_to_select = views[index]; + self.$el.show(); + if (self.active_view !== view_to_select) { + self.on_mode_switch(view_to_select); + } + }, + get_title: function() { + var id; + var currentIndex; + _.each(self.getParent().breadcrumbs, function(bc, i) { + if (bc.widget === self) { + currentIndex = i; + } + }); + var next = self.getParent().breadcrumbs.slice(currentIndex + 1)[0]; + var titles = _.map(views, function(v) { + var controller = self.views[v].controller; + if (v === 'form') { + id = controller.datarecord.id; + } + return controller.get('title'); + }); + if (next && next.action && next.action.res_id && self.dataset && + self.active_view === 'form' && self.dataset.model === next.action.res_model && id === next.action.res_id) { + // If the current active view is a formview and the next item in the breadcrumbs + // is an action on same object (model / res_id), then we omit the current formview's title + titles.pop(); + } + return titles; + } }); - return view_promise; }, /** * Method used internally when a view asks to switch view. This method is meant @@ -388,7 +622,7 @@ instance.web.ViewManager = instance.web.Widget.extend({ this.searchview = new instance.web.SearchView(this, this.dataset, view_id, search_defaults, this.flags.search_view === false); this.searchview.on_search.add(this.do_searchview_search); - return this.searchview.appendTo(this.$element.find(".oe_view_manager_view_search")); + return this.searchview.appendTo(this.$el.find(".oe_view_manager_view_search")); }, do_searchview_search: function(domains, contexts, groupbys) { var self = this, @@ -434,14 +668,6 @@ instance.web.ViewManager = instance.web.Widget.extend({ */ on_action_executed: function () { }, - display_title: function () { - var view = this.views[this.active_view]; - if (view) { - // ick - return view.controller.fields_view.arch.attrs.string; - } - return ''; - } }); instance.web.ViewManagerAction = instance.web.ViewManager.extend({ @@ -486,7 +712,7 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ if (this.session.hidden_menutips) { return; } - this.session.hidden_menutips = {} + this.session.hidden_menutips = {}; }, /** * Initializes the ViewManagerAction: sets up the searchview (if the @@ -513,33 +739,8 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ var manager_ready = $.when(searchview_loaded, main_view_loaded); - this.$element.find('.oe_debug_view').change(this.on_debug_changed); - - if (this.action.help && !this.flags.low_profile) { - var Users = new instance.web.DataSet(self, 'res.users'), - $tips = this.$element.find('.oe_view_manager_menu_tips'); - $tips.delegate('blockquote button', 'click', function() { - var $this = $(this); - //noinspection FallthroughInSwitchStatementJS - switch ($this.attr('name')) { - case 'disable': - Users.write(self.session.uid, {menu_tips:false}); - case 'hide': - $this.closest('blockquote').hide(); - self.session.hidden_menutips[self.action.id] = true; - } - }); - if (!(self.action.id in self.session.hidden_menutips)) { - Users.read_ids([this.session.uid], ['menu_tips']).then(function(users) { - var user = users[0]; - if (!(user && user.id === self.session.uid)) { - return; - } - $tips.find('blockquote').toggle(user.menu_tips); - }); - } - } - + this.$el.find('.oe_debug_view').change(this.on_debug_changed); + this.$el.addClass("oe_view_manager_" + (this.action.target || 'current')); return manager_ready; }, on_debug_changed: function (evt) { @@ -551,7 +752,15 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ switch (val) { case 'fvg': var dialog = new instance.web.Dialog(this, { title: _t("Fields View Get"), width: '95%' }).open(); - $('<pre>').text(instance.web.json_node_to_xml(current_view.fields_view.arch, true)).appendTo(dialog.$element); + $('<pre>').text(instance.web.json_node_to_xml(current_view.fields_view.arch, true)).appendTo(dialog.$el); + break; + case 'tests': + this.do_action({ + name: "JS Tests", + target: 'new', + type : 'ir.actions.act_url', + url: '/web/static/test/test.html' + }) break; case 'perm_read': var ids = current_view.get_selected_ids(); @@ -570,6 +779,17 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ case 'toggle_layout_outline': current_view.rendering_engine.toggle_layout_debugging(); break; + case 'translate': + this.do_action({ + name: "Technical Translation", + res_model : 'ir.translation', + domain : [['type', '!=', 'object'], '|', ['name', '=', this.dataset.model], ['name', 'ilike', this.dataset.model + ',']], + views: [[false, 'list'], [false, 'form']], + type : 'ir.actions.act_window', + view_type : "list", + view_mode : "list" + }); + break; case 'fields': this.dataset.call_and_eval( 'fields_get', [false, {}], null, 1).then(function (fields) { @@ -593,15 +813,6 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ width: '95%'}, $root).open(); }); break; - case 'manage_views': - if (current_view.fields_view && current_view.fields_view.arch) { - var view_editor = new instance.web.ViewEditor(current_view, current_view.$element, this.dataset, current_view.fields_view.arch); - view_editor.start(); - } else { - this.do_warn(_t("Manage Views"), - _t("Could not find current view declaration")); - } - break; case 'edit_workflow': return this.do_action({ res_model : 'workflow', @@ -626,6 +837,21 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ } }); break; + case 'print_workflow': + if (current_view.get_selected_ids && current_view.get_selected_ids().length == 1) { + instance.web.blockUI(); + var action = { + context: { active_ids: current_view.get_selected_ids() }, + report_name: "workflow.instance.graph", + datas: { + model: this.dataset.model, + id: current_view.get_selected_ids()[0], + nested: true, + } + }; + this.session.get_file({ url: '/web/report', data: {action: JSON.stringify(action)}, complete: instance.web.unblockUI }); + } + break; default: if (val) { console.log("No debug handler for ", val); @@ -658,16 +884,22 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ var controller = self.views[self.active_view].controller, fvg = controller.fields_view, view_id = (fvg && fvg.view_id) || '--'; - self.$element.find('.oe_debug_view').html(QWeb.render('ViewManagerDebug', { + self.$el.find('.oe_debug_view').html(QWeb.render('ViewManagerDebug', { view: controller, view_manager: self })); - if (!self.action.name && fvg) { - self.$element.find('.oe_view_title_text').text(fvg.arch.attrs.string || fvg.name); - } - + self.set_title(); }); }, + do_create_view: function(view_type) { + var r = this._super.apply(this, arguments); + var view = this.views[view_type].controller; + view.set({ 'title': this.action.name }); + return r; + }, + set_title: function(title) { + this.$el.find('.oe_breadcrumb_title:first').html(this.getParent().get_title()); + }, do_push_state: function(state) { if (this.getParent() && this.getParent().do_push_state) { state["view_type"] = this.active_view; @@ -689,42 +921,39 @@ instance.web.ViewManagerAction = instance.web.ViewManager.extend({ self.views[self.active_view].controller.do_load_state(state, warm); }); }, - display_title: function () { - return this.action.name; - } }); instance.web.Sidebar = instance.web.Widget.extend({ init: function(parent) { + var self = this; this._super(parent); var view = this.getParent(); this.sections = [ { 'name' : 'print', 'label' : _t('Print'), }, - { 'name' : 'files', 'label' : _t('Attachment'), }, { 'name' : 'other', 'label' : _t('More'), } ]; this.items = { 'print' : [], - 'files' : [], 'other' : [] }; - if (this.session.uid === 1) { - var item = { label: _t("Translate"), callback: view.on_sidebar_translate, title: _t("Technical translation") }; - this.items.other.push(item); - } + this.fileupload_id = _.uniqueId('oe_fileupload'); + $(window).on(this.fileupload_id, function() { + var args = [].slice.call(arguments).slice(1); + if (args[0] && args[0].error) { + alert(args[0].error); + } else { + self.do_attachement_update(self.dataset, self.model_id); + } + instance.web.unblockUI(); + }); }, start: function() { var self = this; this._super(this); this.redraw(); - this.$element.on('click','.oe_dropdown_toggle',function(event) { - $(this).parent().find('ul').toggle(); - return false; - }); - this.$element.on('click','.oe_dropdown_menu li a', function(event) { + this.$el.on('click','.oe_dropdown_menu li a', function(event) { var section = $(this).data('section'); var index = $(this).data('index'); - $(this).closest('ul').hide(); var item = self.items[section][index]; if (item.callback) { item.callback.apply(self, [item]); @@ -733,16 +962,12 @@ instance.web.Sidebar = instance.web.Widget.extend({ } else if (item.url) { return true; } - return false; + event.preventDefault(); }); - //this.$div.html(QWeb.render('FormView.sidebar.attachments', this)); - //this.$element.find('.oe-binary-file').change(this.on_attachment_changed); - //this.$element.find('.oe-sidebar-attachment-delete').click(this.on_attachment_delete); }, redraw: function() { var self = this; - self.$element.html(QWeb.render('Sidebar', {widget: self})); - this.$element.find('ul').hide(); + self.$el.html(QWeb.render('Sidebar', {widget: self})); // Hides Sidebar sections when item list is empty this.$('.oe_form_dropdown_section').each(function() { @@ -826,6 +1051,8 @@ instance.web.Sidebar = instance.web.Widget.extend({ }); }, do_attachement_update: function(dataset, model_id) { + this.dataset = dataset; + this.model_id = model_id; if (!model_id) { this.on_attachments_loaded([]); } else { @@ -837,165 +1064,43 @@ instance.web.Sidebar = instance.web.Widget.extend({ on_attachments_loaded: function(attachments) { var self = this; var items = []; - // TODO: preprend: _s + - var prefix = '/web/binary/saveas?session_id=' + self.session.session_id + '&model=ir.attachment&field=datas&filename_field=name&id='; + var prefix = this.session.origin + '/web/binary/saveas?session_id=' + self.session.session_id + '&model=ir.attachment&field=datas&filename_field=name&id='; _.each(attachments,function(a) { a.label = a.name; if(a.type === "binary") { a.url = prefix + a.id + '&t=' + (new Date().getTime()); } }); - attachments.push( { label: _t("Add..."), callback: self.on_attachment_add } ); self.items['files'] = attachments; self.redraw(); - }, - on_attachment_add: function(e) { - this.$element.find('.oe_sidebar_add').show(); + this.$('.oe_sidebar_add_attachment .oe_form_binary_file').change(this.on_attachment_changed); + this.$el.find('.oe_sidebar_delete_item').click(this.on_attachment_delete); }, on_attachment_changed: function(e) { - return; - window[this.element_id + '_iframe'] = this.do_update; var $e = $(e.target); - if ($e.val() != '') { - this.$element.find('form.oe-binary-form').submit(); + if ($e.val() !== '') { + this.$el.find('form.oe_form_binary_form').submit(); $e.parent().find('input[type=file]').prop('disabled', true); $e.parent().find('button').prop('disabled', true).find('img, span').toggle(); + this.$('.oe_sidebar_add_attachment span').text(_t('Uploading...')); + instance.web.blockUI(); } }, on_attachment_delete: function(e) { - return; - var self = this, $e = $(e.currentTarget); - var name = _.str.trim($e.parent().find('a.oe-sidebar-attachments-link').text()); - if (confirm(_.str.sprintf(_t("Do you really want to delete the attachment %s?"), name))) { - this.rpc('/web/dataset/unlink', { - model: 'ir.attachment', - ids: [parseInt($e.attr('data-id'))] - }, function(r) { - $e.parent().remove(); - self.do_notify("Delete an attachment", "The attachment '" + name + "' has been deleted"); - }); - } - } -}); - -instance.web.TranslateDialog = instance.web.Dialog.extend({ - dialog_title: {toString: function () { return _t("Translations"); }}, - init: function(view) { - // TODO fme: should add the language to fields_view_get because between the fields view get - // and the moment the user opens the translation dialog, the user language could have been changed - this.view_language = view.session.user_context.lang; - this['on_button' + _t("Save")] = this.on_button_Save; - this['on_button' + _t("Close")] = this.on_button_Close; - this._super(view, { - width: '80%', - height: '80%' - }); - this.view = view; - this.view_type = view.fields_view.type || ''; - this.$fields_form = null; - this.$view_form = null; - this.$sidebar_form = null; - this.translatable_fields_keys = _.map(this.view.translatable_fields || [], function(i) { return i.name }); - this.languages = null; - this.languages_loaded = $.Deferred(); - (new instance.web.DataSetSearch(this, 'res.lang', this.view.dataset.get_context(), - [['translatable', '=', '1']])).read_slice(['code', 'name'], { sort: 'id' }).then(this.on_languages_loaded); - }, - start: function() { var self = this; - this._super(); - $.when(this.languages_loaded).then(function() { - self.$element.html(instance.web.qweb.render('TranslateDialog', { widget: self })); - self.$fields_form = self.$element.find('.oe_translation_form'); - self.$fields_form.find('.oe_trad_field').change(function() { - $(this).toggleClass('touched', ($(this).val() != $(this).attr('data-value'))); - }); - }); - return this; - }, - on_languages_loaded: function(langs) { - this.languages = langs; - this.languages_loaded.resolve(); - }, - do_load_fields_values: function(callback) { - var self = this, - deffered = []; - this.$fields_form.find('.oe_trad_field').val('').removeClass('touched'); - _.each(self.languages, function(lg) { - var deff = $.Deferred(); - deffered.push(deff); - var callback = function(values) { - _.each(self.translatable_fields_keys, function(f) { - self.$fields_form.find('.oe_trad_field[name="' + lg.code + '-' + f + '"]').val(values[0][f] || '').attr('data-value', values[0][f] || ''); - }); - deff.resolve(); - }; - if (lg.code === self.view_language) { - var values = {}; - _.each(self.translatable_fields_keys, function(field) { - values[field] = self.view.fields[field].get_value(); - }); - callback([values]); - } else { - self.rpc('/web/dataset/get', { - model: self.view.dataset.model, - ids: [self.view.datarecord.id], - fields: self.translatable_fields_keys, - context: self.view.dataset.get_context({ - 'lang': lg.code - })}, callback); - } - }); - $.when.apply(null, deffered).then(callback); - }, - open: function(field) { - var self = this, - sup = this._super; - $.when(this.languages_loaded).then(function() { - if (self.view.translatable_fields && self.view.translatable_fields.length) { - self.do_load_fields_values(function() { - sup.call(self); - if (field) { - var $field_input = self.$element.find('tr[data-field="' + field.name + '"] td:nth-child(2) *:first-child'); - self.$element.scrollTo($field_input); - $field_input.focus(); - } - }); - } else { - sup.call(self); - } - }); - }, - on_button_Save: function() { - var trads = {}, - self = this, - trads_mutex = new $.Mutex(); - self.$fields_form.find('.oe_trad_field.touched').each(function() { - var field = $(this).attr('name').split('-'); - if (!trads[field[0]]) { - trads[field[0]] = {}; - } - trads[field[0]][field[1]] = $(this).val(); - }); - _.each(trads, function(data, code) { - if (code === self.view_language) { - _.each(data, function(value, field) { - self.view.fields[field].set_value(value); - }); - } - trads_mutex.exec(function() { - return self.view.dataset.write(self.view.datarecord.id, data, { context : { 'lang': code } }); + e.preventDefault(); + e.stopPropagation(); + var self = this; + var $e = $(e.currentTarget); + if (confirm(_t("Do you really want to delete this attachment ?"))) { + (new instance.web.DataSet(this, 'ir.attachment')).unlink([parseInt($e.attr('data-id'), 10)]).then(function() { + self.do_attachement_update(self.dataset, self.model_id); }); - }); - this.close(); - }, - on_button_Close: function() { - this.close(); + } } }); instance.web.View = instance.web.Widget.extend({ - template: "EmptyComponent", // name displayed in view switchers display_name: '', /** @@ -1008,7 +1113,7 @@ instance.web.View = instance.web.Widget.extend({ this.view_id = view_id; this.set_default_options(options); }, - start: function() { + start: function () { return this.load_view(); }, load_view: function() { @@ -1046,12 +1151,6 @@ instance.web.View = instance.web.Widget.extend({ action_views_ids: {} }); }, - open_translate_dialog: function(field) { - if (!this.translate_dialog) { - this.translate_dialog = new instance.web.TranslateDialog(this).start(); - } - this.translate_dialog.open(field); - }, /** * Fetches and executes the action identified by ``action_data``. * @@ -1116,11 +1215,11 @@ instance.web.View = instance.web.Widget.extend({ } } args.push(context); - return dataset.call_button(action_data.name, args, handler); + return dataset.call_button(action_data.name, args).then(handler); } else if (action_data.type=="action") { - return this.rpc('/web/action/load', { action_id: parseInt(action_data.name, 10), context: context, do_not_eval: true}, handler); + return this.rpc('/web/action/load', { action_id: action_data.name, context: context, do_not_eval: true}, handler); } else { - return dataset.exec_workflow(record_id, action_data.name, handler); + return dataset.exec_workflow(record_id, action_data.name).then(handler); } }, /** @@ -1134,10 +1233,10 @@ instance.web.View = instance.web.Widget.extend({ this.embedded_view = embedded_view; }, do_show: function () { - this.$element.show(); + this.$el.show(); }, do_hide: function () { - this.$element.hide(); + this.$el.hide(); }, do_push_state: function(state) { if (this.getParent() && this.getParent().do_push_state) { @@ -1164,23 +1263,8 @@ instance.web.View = instance.web.Widget.extend({ }, do_search: function(view) { }, - on_sidebar_import: function() { - var import_view = new instance.web.DataImport(this, this.dataset); - import_view.start(); - }, on_sidebar_export: function() { - var export_view = new instance.web.DataExport(this, this.dataset); - export_view.start(); - }, - on_sidebar_translate: function() { - return this.do_action({ - res_model : 'ir.translation', - domain : [['type', '!=', 'object'], '|', ['name', '=', this.dataset.model], ['name', 'ilike', this.dataset.model + ',']], - views: [[false, 'list'], [false, 'form']], - type : 'ir.actions.act_window', - view_type : "list", - view_mode : "list" - }); + new instance.web.DataExport(this, this.dataset).open(); }, sidebar_context: function () { return $.when(); @@ -1191,6 +1275,15 @@ instance.web.View = instance.web.Widget.extend({ */ reload: function () { return $.when(); + }, + /** + * Return whether the user can perform the action ('create', 'edit', 'delete') in this view. + * An action is disabled by setting the corresponding attribute in the view's main element, + * like: <form string="" create="false" edit="false" delete="false"> + */ + is_action_enabled: function(action) { + var attrs = this.fields_view.arch.attrs; + return (action in attrs) ? JSON.parse(attrs[action]) : true; } }); @@ -1218,7 +1311,6 @@ instance.web.xml_to_json = function(node) { } instance.web.json_node_to_xml = function(node, human_readable, indent) { // For debugging purpose, this function will convert a json node back to xml - // Maybe useful for xml view editor indent = indent || 0; var sindent = (human_readable ? (new Array(indent + 1).join('\t')) : ''), r = sindent + '<' + node.tag, @@ -1227,7 +1319,9 @@ instance.web.json_node_to_xml = function(node, human_readable, indent) { if (typeof(node) === 'string') { return sindent + node; } else if (typeof(node.tag) !== 'string' || !node.children instanceof Array || !node.attrs instanceof Object) { - throw("Node a json node"); + throw new Error( + _.str.sprintf("Node [%s] is not a JSONified XML node", + JSON.stringify(node))); } for (var attr in node.attrs) { var vattr = node.attrs[attr]; diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 3764395dc13..fdf39633186 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -25,11 +25,18 @@ </div> </div> </t> +<t t-name="Tipsy.alert"> + <a class="oe_tooltip_close oe_e">[</a> + <span style="float:left; margin:2px 5px 0 0;" class="ui-icon ui-icon-alert ui-state-error"></span> + <div class="oe_tooltip_message"> + <t t-esc="message"/> + </div> +</t> <t t-name="CrashManager.warning"> - <table cellspacing="0" cellpadding="0" border="0" class="oe-dialog-warning"> + <table cellspacing="0" cellpadding="0" border="0" class="oe_dialog_warning"> <tr> - <td><img t-att-src='_s + "/web/static/src/img/warning.png"' class="oe-dialog-icon"/></td> + <td><img t-att-src='_s + "/web/static/src/img/warning.png"' class="oe_dialog_icon"/></td> <td> <p> <t t-js="d"> @@ -44,29 +51,6 @@ </table> </t> <t t-name="CrashManager.error"> - <t t-if="!session.openerp_entreprise"> - <span>Your version of OpenERP is unsupported. Support & maintenance services are available here: <a href="http://www.openerp.com/support-or-publisher-warranty-contract" target="_blank">OpenERP Entreprise</a>.</span> - </t> - <t t-if="session.openerp_entreprise"> - <div class="oe_error_send"> - <div> - <div class="oe_centeralign"><b>OpenERP Enterprise Contract.</b></div> - <div><br/>Your report will be sent to the OpenERP Enterprise team.<br/></div><br/> - <div> - <label>Summary:</label><br/> - <input id="issuename" type="text" class="oe_fielddiv"/> - </div><br/><br/> - <div> - <label>Description:</label><br/> - <textarea id="explanation" rows="6"></textarea> - </div><br/><br/> - <div> - <label>What you did:</label><br/> - <textarea id="remark" rows="6" ></textarea> - </div> - </div> - </div><br/> - </t> <div class="oe_error_detail"> <pre><t t-esc="error.message"/></pre> <hr/> @@ -77,7 +61,7 @@ <t t-name="Login"> <div class="oe_login"> <div class="oe_login_bottom"> </div> - <div class="oe_login_error_message">Invalid username or password</div> + <div class="oe_login_error_message"/> <div class="oe_login_pane"> <div class="oe_login_logo"><img src='/web/static/src/img/logo2.png'/></div> <form action="" method="post"> @@ -95,7 +79,7 @@ </form> <div class="oe_login_footer"> <a href="#" class="oe_login_manage_db">Manage Databases</a> | - <a href="http://www.openerp.com">Powered by <span>OpenERP</span></a> + <a href="http://www.openerp.com" target="_blank">Powered by <span>OpenERP</span></a> </div> </div> </div> @@ -114,68 +98,80 @@ </select> </t> + <t t-name="DatabaseManager"> - <div class="oe_database_manager"> - <div class="oe_database_manager_menu"> - <ul class="oe_form_notebook"> - <li><a href="#db_create">Create</a></li> - <li><a href="#db_drop">Drop</a></li> - <li><a href="#db_backup">Backup</a></li> - <li><a href="#db_restore">Restore</a></li> - <li><a href="#db_change_password">Password</a></li> - <li><a id="back_to_login" href="#">Back to Login</a></li> - </ul> - <form id="db_create" name="create_db_form" method="POST"> + <div class="oe_view_manager_view_form"> + <div class="oe_form"> + <form id="db_create" name="create_db_form" style="display: block;"> + <div class="oe_view_manager oe_view_manager_current"> + <div class="oe_view_manager_header" style="padding: 8px;"> + <div class="oe_header_row"> + <h2 class="oe_view_title"> + <span class="oe_view_title_text oe_breadcrumb_title">Create Database</span> + </h2> + <button type="submit" class="oe_button oe_highlight db_create">Create</button> + </div> + </div> + </div> <table align="center" class="db_option_table"> <tr> - <th colspan="2" class="option_string"> CREATE DATABASE </th> - </tr> - <tr> <td><label for="super_admin_pwd">Master password:</label></td> - <td><input type="password" name="super_admin_pwd" class="required" value="admin"/></td> + <td><input type="password" name="super_admin_pwd" class="required" value="admin" /></td> </tr> <tr> <td><label for="db_name">New database name:</label></td> - <td><input type="text" name="db_name" class="required" matches="^[a-zA-Z][a-zA-Z0-9_]+$" autofocus="true"/></td> + <td><input type="text" name="db_name" class="required" matches="^[a-zA-Z][a-zA-Z0-9_-]+$" autofocus="true"/></td> </tr> <tr> <td><label for="demo_data">Load Demonstration data:</label></td> - <td><input type="checkbox" name="demo_data"/></td> + <td class="oe_form_group_cell"> + <span class="oe_form_field oe_form_field_boolean"> + <input type="checkbox" name="demo_data" /> + </span> + </td> </tr> <tr> <td><label for="db_lang">Default language:</label></td> - <td> + <td class="oe_form_field oe_form_field_selection "> <select name="db_lang" t-if="widget.lang_list"> <t t-foreach="widget.lang_list" t-as="lang"> - <option t-att-value="lang[0]" t-att-selected="lang[0] === 'en_US' ? 'selected' : undefined"><t t-esc="lang[1]"/></option> + <option t-att-value="lang[0]" t-att-selected="lang[0] === 'en_US' ? 'selected' : undefined"> + <t t-esc="lang[1]" /> + </option> </t> </select> </td> </tr> <tr> <td><label for="create_admin_pwd">Admin password:</label></td> - <td><input type="password" name="create_admin_pwd" class="required"/></td> + <td><input type="password" name="create_admin_pwd" class="required" /></td> </tr> <tr> <td><label for="create_confirm_pwd">Confirm password:</label></td> <td><input type="password" name="create_confirm_pwd" class="required" equalTo="input[name=create_admin_pwd]"/></td> </tr> - <tr> - <td colspan="2" align="right"><button class="oe_button">Create</button></td> - </tr> </table> </form> - <form id="db_drop" name="drop_db_form" method="POST"> + <form id="db_drop" name="drop_db_form" style="display: none; "> + <div class="oe_view_manager oe_view_manager_current"> + <div class="oe_view_manager_header" style="padding: 8px;"> + <div class="oe_header_row"> + <h2 class="oe_view_title"> + <span class="oe_view_title_text oe_breadcrumb_title">Drop Database</span> + </h2> + <button type="submit" class="oe_button oe_highlight db_drop">Drop</button> + </div> + </div> + </div> <table align="center" class="db_option_table"> <tr> - <th colspan="2" class="option_string"> DROP DATABASE </th> - </tr> - <tr> <td><label for="drop_db">Database:</label></td> - <td> + <td class="oe_form_field oe_form_field_selection"> <select t-if="widget.db_list" name="drop_db" autofocus="autofocus"> <t t-foreach="widget.db_list" t-as="db"> - <option t-att-value="db"><t t-esc="db"/></option> + <option t-att-value="db"> + <t t-esc="db" /> + </option> </t> </select> <input t-if="!widget.db_list" name="drop_db" class="required" type="text" autofocus="autofocus"/> @@ -183,25 +179,31 @@ </tr> <tr> <td><label for="drop_password">Master Password:</label></td> - <td><input type="password" name="drop_pwd" class="required"/></td> - </tr> - <tr> - <td colspan="2" align="right"><button class="oe_button">Drop</button></td> + <td><input type="password" name="drop_pwd" class="required" /></td> </tr> </table> </form> - <form id="db_backup" name="backup_db_form" method="POST" target="backup-target" action="/web/database/backup"> - <input type="hidden" name="token"/> + <form id="db_backup" name="backup_db_form" target="backup-target" action="/web/database/backup" style="display: none;"> + <div class="oe_view_manager oe_view_manager_current"> + <div class="oe_view_manager_header" style="padding: 8px;"> + <div class="oe_header_row"> + <h2 class="oe_view_title"> + <span class="oe_view_title_text oe_breadcrumb_title">Backup Database</span> + </h2> + <button type="submit" class="oe_button oe_highlight db_backup">Backup</button> + </div> + </div> + </div> + <input type="hidden" name="token" /> <table align="center" class="db_option_table"> <tr> - <th colspan="2" class="option_string"> BACKUP DATABASE </th> - </tr> - <tr> <td><label for="backup_db">Database:</label></td> - <td> + <td class="oe_form_field oe_form_field_selection "> <select t-if="widget.db_list" name="backup_db" autofocus="autofocus"> <t t-foreach="widget.db_list" t-as="db"> - <option t-att-value="db"><t t-esc="db"/></option> + <option t-att-value="db"> + <t t-esc="db" /> + </option> </t> </select> <input t-if="!widget.db_list" name="backup_db" class="required" type="text" autofocus="autofocus"/> @@ -209,19 +211,23 @@ </tr> <tr> <td><label for="backup_pwd">Master Password:</label></td> - <td><input type="password" name="backup_pwd" class="required"/></td> - </tr> - <tr> - <td colspan="2" align="right"><button class="oe_button">Backup</button></td> + <td><input type="password" name="backup_pwd" class="required" /></td> </tr> </table> </form> - <form id="db_restore" name="restore_db_form" method="POST"> + <form id="db_restore" name="restore_db_form" style="display: none; "> + <div class="oe_view_manager oe_view_manager_current"> + <div class="oe_view_manager_header" style="padding: 8px;"> + <div class="oe_header_row"> + <h2 class="oe_view_title"> + <span class="oe_view_title_text oe_breadcrumb_title">Restore Database</span> + </h2> + <button type="submit" class="oe_button oe_highlight db_restore">Restore</button> + </div> + </div> + </div> <table align="center" class="db_option_table"> <tr> - <th colspan="2" class="option_string"> RESTORE DATABASE </th> - </tr> - <tr> <td><label for="restore_db">File:</label></td> <td><input type="file" name="db_file" class="required" autofocus="autofocus"/></td> </tr> @@ -233,17 +239,21 @@ <td><label for="new_db">New database name:</label></td> <td><input type="text" name="new_db" class="required"/></td> </tr> - <tr> - <td colspan="2" align="right"><button class="oe_button">Restore</button></td> - </tr> </table> </form> - <form id="db_change_password" name="change_pwd_form" method="POST"> + <form id="db_change_password" name="change_pwd_form" style="display: none;"> + <div class="oe_view_manager oe_view_manager_current"> + <div class="oe_view_manager_header" style="padding: 8px;"> + <div class="oe_header_row"> + <h2 class="oe_view_title"> + <span class="oe_view_title_text oe_breadcrumb_title">Change Master Password</span> + </h2> + <button type="submit" class="oe_button oe_highlight db-change-password">Change Password</button> + </div> + </div> + </div> <table align="center" class="db_option_table"> <tr> - <th colspan="2" class="option_string"> CHANGE MASTER PASSWORD </th> - </tr> - <tr> <td><label for="old_pwd">Master password:</label></td> <td><input type="password" name="old_pwd" class="required" minlength="1" autofocus="autofocus"/></td> </tr> @@ -253,28 +263,67 @@ </tr> <tr> <td><label for="confirm_pwd">Confirm new master password:</label></td> - <td><input type="password" name="confirm_pwd" class="required" equalTo="input[name=new_pwd]" minlength="1"/></td> - </tr> - <tr> - <td colspan="2" align="right"><button class="oe_button">Change Password</button></td> + <td><input type="password" name="confirm_pwd" class="required" equalTo="input[name=new_pwd]" minlength="1"/> </td> </tr> </table> </form> </div> </div> </t> +<t t-name="DatabaseManager.user_menu"> + <span class="oe_right"> + <a id="back-to-login" href="#"><span class="oe_topbar_item oe_topbar_name">Back to Login</span></a> + </span> +</t> +<t t-name="DatabaseManager.menu"> + <div class="oe_secondary_menu_section">Database Management</div> + <ul class="oe_secondary_submenu"> + <li><a href="#db_create">Create</a></li> + <li><a href="#db_drop">Drop</a></li> + <li><a href="#db_backup">Backup</a></li> + <li><a href="#db_restore">Restore</a></li> + <li><a href="#db_change_password">Password</a></li> + </ul> +</t> + +<t t-name="ChangePassword"> + <form name="change_password_form" method="POST"> + <table align="center"> + <tr> + <td><label for="old_pwd">Old Password:</label></td> + <td><input type="password" name="old_pwd" + minlength="1" autofocus="autofocus"/></td> + </tr> + <tr> + <td><label for="new_password">New Password:</label></td> + <td><input type="password" name="new_password" + minlength="1"/></td> + </tr> + <tr> + <td><label for="confirm_pwd">Confirm Password:</label></td> + <td><input type="password" name="confirm_pwd" + minlength="1"/></td> + </tr> + <tr> + <td colspan="2" align="right"><button class="oe_button">Change Password</button></td> + </tr> + </table> + </form> +</t> <t t-name="Menu"> <ul class="oe_menu" t-if="widget.data"> <li t-foreach="widget.data.data.children" t-as="menu"> - <t t-call="Menu.secondary.link"/> + <t t-call="Menu.link"/> </li> </ul> </t> <t t-name="Menu.more"> <li class="oe_menu_more_container"> - <a href="#" class="oe_menu_more_link oe_dropdown_toggle">More</a> - <ul class="oe_menu_more" style="display: none;"/> + <span class="oe_topbar_item oe_menu_more_link oe_dropdown_toggle oe_dropdown_arrow"> + More + <ul class="oe_menu_more oe_dropdown_menu"/> + </span> </li> </t> <t t-name="Menu.secondary"> @@ -284,7 +333,7 @@ <t t-esc="menu.name"/> <!-- Shall the section be still clickable ? - <t t-call="Menu.secondary.link"/> + <t t-call="Menu.link"/> --> </div> <t t-call="Menu.secondary.submenu"/> @@ -294,21 +343,21 @@ <t t-name="Menu.secondary.submenu"> <ul t-if="menu.children.length" class="oe_secondary_submenu"> <li t-foreach="menu.children" t-as="menu"> - <t t-call="Menu.secondary.link"/> + <t t-call="Menu.link"/> <!--<span class="oe_menu_label">8</span>--> <t t-call="Menu.secondary.submenu"/> </li> </ul> </t> -<t t-name="Menu.secondary.link"> - <a t-attf-href="#menu_id=#{menu.id}&action_id=#{menu.action ? menu.action.split(',')[1] : ''}" +<t t-name="Menu.link"> + <a t-attf-href="#menu_id=#{menu.id}&action=#{menu.action ? menu.action.split(',')[1] : ''}" t-att-class="menu.children.length ? 'oe_menu_toggler' : 'oe_menu_leaf'" t-att-data-menu="menu.id" t-att-data-action-model="menu.action ? menu.action.split(',')[0] : ''" t-att-data-action-id="menu.action ? menu.action.split(',')[1] : ''"> <t t-esc="menu.name"/> <t t-if="menu.needaction_enabled and menu.needaction_counter"> - <div class="oe_menu_counter"> + <div class="oe_tag oe_tag_dark oe_menu_counter"> <t t-esc="menu.needaction_counter"/> </div> </t> @@ -316,63 +365,35 @@ </t> <t t-name="UserMenu"> - <ul class="oe_user_menu oe_topbar_item"> - <li class="oe_dropdown"> - <a href="#" class="oe_dropdown_toggle"> - <img class="oe_topbar_avatar" t-att-data-default-src="_s + '/web/static/src/img/user_menu_avatar.png'"/> - <span class="oe_topbar_name"/> - </a> - <ul class="oe_dropdown_options"> - <li><a href="#" data-menu="about">About OpenERP</a></li> - <li><a href="#" data-menu="settings">Preferences</a></li> - <li><a href="#" data-menu="logout">Log out</a></li> - </ul> - </li> - </ul> + <span class="oe_user_menu oe_topbar_item oe_dropdown_toggle oe_dropdown_arrow"> + <img class="oe_topbar_avatar" t-att-data-default-src="_s + '/web/static/src/img/user_menu_avatar.png'"/> + <span class="oe_topbar_name"/> + <ul class="oe_dropdown_menu"> + <li><a href="#" data-menu="about">About OpenERP</a></li> + <li><a href="#" data-menu="settings">Preferences</a></li> + <li><a href="#" data-menu="logout">Log out</a></li> + </ul> + </span> </t> <t t-name="UserMenu.about"> - <div> - <a class="oe_activate_debug_mode" href="?debug" style="float:right; font-size: 80%;">Activate the developer mode</a> - <h1 style="margin:0;">OpenERP</h1> - <h3 style="margin:15px 0;padding:0;">Version <t t-esc="version_info.version"/></h3> - <p> - Copyright © 2004-TODAY OpenERP SA. All Rights Reserved.<br /> - OpenERP is a trademark of the <a target="_blank" href="http://openerp.com/" style="text-decoration: underline;">OpenERP SA Company</a>. - </p> - <p> - Licenced under the terms of <a target="_blank" href="http://www.gnu.org/licenses/agpl.html" style="text-decoration: underline;">GNU Affero General Public License</a> - </p> - <p> - For more information visit <a target="_blank" href="http://openerp.com/" style="text-decoration: underline;">OpenERP.com</a> - </p> + + <div class="oe_about"> + <a class="oe_activate_debug_mode oe_right" href="?debug" style="background-color: white; padding:2px 6px; border-radius: 10px;">Activate the developer mode</a> + <img class="oe_logo" src="/web/static/src/img/logo2.png"/> + <h3>Version <t t-esc="version_info.version"/></h3> + + <div class="oe_bottom"> + <p>Copyright © 2004-TODAY OpenERP SA. All Rights Reserved.<br /> + OpenERP is a trademark of the <a target="_blank" href="http://openerp.com/" style="text-decoration: underline;">OpenERP SA Company</a>.</p> + <p>Licenced under the terms of <a target="_blank" href="http://www.gnu.org/licenses/agpl.html" style="text-decoration: underline;">GNU Affero General Public License</a></p> + <p>For more information visit <a target="_blank" href="http://openerp.com/" style="text-decoration: underline;">OpenERP.com</a></p> + </div> </div> -</t> -<t t-name="UserMenu.password"> - <form name="change_password_form" method="POST"> - <table align="center"> - <tr> - <td><label for="old_pwd">Old Password:</label></td> - <td><input type="password" name="old_pwd" - minlength="1" autofocus="autofocus"/></td> - </tr> - <tr> - <td><label for="new_password">New Password:</label></td> - <td><input type="password" name="new_password" - minlength="1" autofocus="autofocus"/></td> - </tr> - <tr> - <td><label for="confirm_pwd">Confirm Password:</label></td> - <td><input type="password" name="confirm_pwd" - minlength="1"/></td> - </tr> - <tr> - <td colspan="2" align="right"><button class="oe_button">Change Password</button></td> - </tr> - </table> - </form> + </t> <t t-name="WebClient"> + <div class="openerp openerp_webclient_container"> <table class="oe_webclient"> <tr> <td colspan="2" class="oe_topbar"> @@ -383,34 +404,39 @@ </tr> <tr> <td class="oe_leftbar" valign="top"> - <t t-js="d"> - d.url = '/' + (window.location.search || ''); - </t> - <a t-att-href="url" class="oe_logo"><img t-att-src='_s + "/web/static/src/img/logo.png"'/></a> + <a class="oe_logo" href="#"><img t-att-src='_s + "/web/static/src/img/logo.png"'/></a> <div class="oe_secondary_menus_container"/> <div class="oe_footer"> - Powered by <a href="http://www.openerp.com"><span>Open</span>ERP</a> + Powered by <a href="http://www.openerp.com" target="_blank"><span>Open</span>ERP</a> </div> </td> <td class="oe_application"> </td> </tr> </table> + </div> +</t> + +<t t-name="EmbedClient"> + <div class="openerp"> + <div class="oe_application"></div> + </div> </t> <t t-name="ViewManager"> <div class="oe_view_manager"> <table class="oe_view_manager_header"> - <col width="33%"/> - <col width="33%"/> - <col width="34%"/> - <tr class="oe_header_row"> + <col width="20%"/> + <col width="35%"/> + <col width="15%"/> + <col width="30%"/> + <tr class="oe_header_row oe_header_row_top"> <td colspan="2"> <h2 class="oe_view_title" t-if="widget.flags.display_title !== false"> - <span class="oe_view_title_text"><t t-esc="widget.display_title()"/></span> + <span class="oe_view_title_text oe_breadcrumb_title"/> </h2> </td> - <td> + <td colspan="2"> <div class="oe_view_manager_view_search" t-opentag="true"/> </td> </tr> @@ -418,13 +444,13 @@ <td> <div class="oe_view_manager_buttons"/> </td> - <td> + <td colspan="2"> <div class="oe_view_manager_sidebar"/> </td> <td> <ul class="oe_view_manager_switch oe_button_group oe_right"> - <t t-if="widget.views_src.length > 2" t-foreach="widget.views_src" t-as="view"> - <li t-if="view.view_type != 'form'" class="oe_e"><a t-attf-class="oe_vm_switch_#{view.view_type}" t-att-data-view-type="view.view_type" t-att-title="_t('Switch to ') + (view.label || view.view_type)"> + <t t-if="widget.views_src.length > 1" t-foreach="widget.views_src" t-as="view"> + <li class="oe_e"><a t-attf-class="oe_vm_switch_#{view.view_type}" t-att-data-view-type="view.view_type" t-att-title="_t('Switch to ') + (view.label || view.view_type)"> </a></li> </t> </ul> @@ -433,7 +459,6 @@ </tr> </table> - <div class="oe_view_manager_body"> <t t-foreach="widget.views_src" t-as="view"> <div t-attf-class="oe_view_manager_view_#{view.view_type}"/> @@ -451,15 +476,18 @@ <option value="">Debug View#<t t-esc="view.fields_view.view_id"/></option> <option t-if="view_manager.active_view === 'form'" value="perm_read" data-views="form,page">View Log (perm_read)</option> <option t-if="view_manager.active_view === 'form'" value="toggle_layout_outline">Toggle Form Layout Outline</option> + <option value="tests">JS Tests</option> <option value="fields">View Fields</option> <option value="fvg">Fields View Get</option> <option value="manage_filters">Manage Filters</option> <t t-if="view_manager.session.uid === 1"> + <option value="translate">Technical translation</option> <option value="manage_views">Manage Views</option> <option value="edit" data-model="ir.ui.view" t-att-data-id="view.fields_view.view_id">Edit <t t-esc="_.str.capitalize(view.fields_view.type)"/>View</option> <option t-if="view_manager.searchview" value="edit" data-model="ir.ui.view" t-att-data-id="view_manager.searchview.view_id">Edit SearchView</option> <option t-if="view_manager.action" value="edit" t-att-data-model="view_manager.action.type" t-att-data-id="view_manager.action.id">Edit Action</option> <option value="edit_workflow">Edit Workflow</option> + <option value="print_workflow">Print Workflow</option> </t> </t> <t t-name="ViewManagerDebugViewLog"> @@ -511,66 +539,45 @@ <div class="oe_sidebar"> <t t-foreach="widget.sections" t-as="section"> <div class="oe_form_dropdown_section"> - <button class="oe_dropdown_toggle"><t t-esc="section.label"/></button> + <button class="oe_dropdown_toggle oe_dropdown_arrow"> + <t t-if="section.name == 'files'" t-raw="widget.items[section.name].length || ''"/> + <t t-esc="section.label"/> + </button> <ul class="oe_dropdown_menu"> <li t-foreach="widget.items[section.name]" t-as="item" t-att-class="item.classname"> <a class="oe_sidebar_action_a" t-att-title="item.title" t-att-data-section="section.name" t-att-data-index="item_index" t-att-href="item.url" target="_blank"> <t t-raw="item.label"/> </a> + <a t-if="section.name == 'files'" class="oe_sidebar_delete_item" t-att-data-id="item.id" title="Delete this attachment">x</a> </li> - <li t-if="section.name == 'files'" class="oe_sidebar_add" style="display:none;"> - Input type file stuff + <li t-if="section.name == 'files'" class="oe_sidebar_add_attachment"> + <t t-call="HiddenInputFile"> + <t t-set="fileupload_id" t-value="widget.fileupload_id"/> + <t t-set="fileupload_action">/web/binary/upload_attachment</t> + <input type="hidden" name="model" t-att-value="widget.dataset and widget.dataset.model"/> + <input type="hidden" name="id" t-att-value="widget.model_id"/> + <input type="hidden" name="session_id" t-att-value="widget.session.session_id"/> + <span>Add...</span> + </t> </li> </ul> </div> </t> </div> </t> -<t t-name="Sidebar.attachments"> - <div class="oe-sidebar-attachments-toolbar"> - <div class="oe-binary-file-set" style="float: right"> - <form class="oe-binary-form" t-attf-target="#{element_id}_iframe" - method="post" enctype="multipart/form-data" action="/web/binary/upload_attachment"> - <input type="hidden" name="session_id" t-att-value="session.session_id"/> - <input type="hidden" name="callback" t-attf-value="#{element_id}_iframe"/> - <input type="hidden" name="model" t-att-value="view.dataset.model"/> - <input type="hidden" name="id" t-att-value="view.datarecord.id"/> - <button class="oe_button" type="button"> - <img t-att-src='_s + "/web/static/src/img/throbber.gif"' width="16" height="16" style="display: none"/> - <span>Add</span> - </button> - <input type="file" class="oe-binary-file" name="ufile" title="Add attachment" - t-att-onclick="view.datarecord.id ? null : 'alert(\'No record selected ! You can only attach to existing record.\'); return false;'"/> - </form> - <iframe t-attf-id="#{element_id}_iframe" t-attf-name="#{element_id}_iframe" style="display: none"/> - </div> - </div> - <br style="clear: both"/> - <ul class="oe-sidebar-attachments-items"> - <li t-foreach="attachments" t-as="attachment"> - <t t-if="attachment.type == 'binary'" t-set="attachment.url" t-value="_s + '/web/binary/saveas?session_id=' - + session.session_id + '&model=ir.attachment&id=' + attachment.id - + '&field=datas&filename_field=name&t=' + (new Date().getTime())"/> - <a class="oe-sidebar-attachments-link" t-att-href="attachment.url" target="_blank"> - <t t-esc="attachment.name"/> - </a> - <a href="#" class="oe-sidebar-attachment-delete" t-att-data-id="attachment.id" t-attf-title="Delete the attachment #{attachment.name}"> - <img t-att-src='_s + "/web/static/src/img/attachments-close.png"' width="15" height="15" border="0"/> - </a> - </li> - </ul> -</t> <t t-name="TreeView"> - <select t-if="toolbar" style="width: 30%"> - </select> + <div class = "tree_header"> + <select t-if="toolbar" ></select> + <button id = "tree_view_collapse">Collapse All</button> + <button id = "tree_view_expand">Expand All</button> + </div> <table class="oe-treeview-table"> <thead> <tr> <th t-foreach="fields_view" t-as="field" - t-if="!field.attrs.modifiers.tree_invisible" - class="treeview-header"> - <t t-esc="fields[field.attrs.name].string" /> + t-if="!field.attrs.modifiers.tree_invisible"> + <t t-esc="field_value.attrs.string || fields[field.attrs.name].string" /> </th> </tr> </thead> @@ -581,11 +588,11 @@ <tr t-name="TreeView.rows" t-foreach="records" t-as="record" t-att-id="'treerow_' + record.id" - t-att-data-id="record.id" t-att-data-level="level + 1"> + t-att-data-id="record.id" t-att-data-level="level"> <t t-set="children" t-value="record[children_field]"/> <t t-set="class" t-value="children and children.length ? 'treeview-tr' : 'treeview-td'"/> <t t-set="rank" t-value="'oe-treeview-first'"/> - <t t-set="style" t-value="'background-position: ' + 19*level + 'px; padding-left: ' + 19*level + 'px;'"/> + <t t-set="style" t-value="'background-position: ' + 19*(level-1) + 'px; padding-left: ' + (4 + 19*(level-1)) + 'px;'"/> <td t-foreach="fields_view" t-as="field" t-if="!field.attrs.modifiers.tree_invisible" @@ -603,7 +610,7 @@ </td> </tr> -<table t-name="ListView" class="oe-listview-content"> +<table t-name="ListView" class="oe_list_content"> <t t-set="columns_count" t-value="visible_columns.length + (options.selectable ? 1 : 0) + (options.deletable ? 1 : 0)"/> <thead> <tr t-if="(!!options.action_buttons and !options.$buttons) or (!!options.pager and !options.$pager)"> @@ -613,28 +620,28 @@ <div class="oe_list_pager"/> </th> </tr> - <tr t-if="options.header" class="oe-listview-header-columns"> + <tr t-if="options.header" class="oe_list_header_columns"> <t t-foreach="columns" t-as="column"> <th t-if="column.meta"> <t t-esc="column.string"/> </th> </t> <th t-if="options.selectable" width="1" > - <input type="checkbox" class="all-record-selector"/> + <input type="checkbox" class="oe_list_record_selector"/> </th> <t t-foreach="columns" t-as="column"> - <th t-if="!column.meta and column.invisible !== '1'" t-att-data-id="column.id" - t-att-class="((options.sortable and column.tag !== 'button') ? 'oe-sortable' : null)"> + <th t-if="!column.meta and column.invisible !== '1' and !column.reorderable" t-att-data-id="column.id" + t-attf-class="oe_list_header_#{column.widget or column.type} #{((options.sortable and column.tag !== 'button') ? 'oe_sortable' : null)}"> <t t-if="column.tag !== 'button'"><t t-esc="column.string"/></t> </th> </t> - <th t-if="options.deletable" width="1"/> + <th t-if="options.deletable" class="oe_list_record_delete" width="13px"/> </tr> </thead> <tfoot> <tr> <td t-if="options.selectable"/> - <td t-foreach="aggregate_columns" t-as="column" class="oe-list-footer oe_number" + <td t-foreach="aggregate_columns" t-as="column" class="oe_list_footer oe_number" t-att-data-field="column.id" t-att-title="column.label"> </td> <td t-if="options.deletable"/> @@ -642,17 +649,16 @@ </tfoot> </table> <div t-name="ListView.buttons" class="oe_list_buttons"> - <t t-if="!widget.no_leaf and widget.options.action_buttons !== false"> - <button type="button" class="oe_button oe_list_add oe_form_button_hi" t-if="widget.options.addable"> + <t t-if="!widget.no_leaf and widget.options.action_buttons !== false and widget.options.addable and widget.is_action_enabled('create')"> + <button type="button" class="oe_button oe_list_add oe_highlight"> <t t-esc="widget.options.addable"/> </button> - <span class="oe_fade">or</span> <a href="#" class="oe_bold oe_list_button_import">Import</a> </t> </div> <t t-name="ListView.pager"> <div class="oe_list_pager" t-att-colspan="widget.columns_count"> <t t-if="!widget.no_leaf and widget.options.pager !== false" t-call="ViewPager"> - <span class="oe-pager-state"> + <span class="oe_list_pager_state"> </span> </t> </div> @@ -660,40 +666,55 @@ <t t-name="ListView.rows" t-foreach="records.length" t-as="index"> <t t-call="ListView.row"> <t t-set="record" t-value="records.at(index)"/> - <t t-set="row_parity" t-value="index_parity"/> </t> </t> -<tr t-name="ListView.row" t-att-class="row_parity" +<tr t-name="ListView.row" t-att-data-id="record.get('id')" t-att-style="view.style_for(record)"> + <t t-set="asData" t-value="record.toForm().data"/> <t t-foreach="columns" t-as="column"> - <td t-if="column.meta"> - - </td> + <td t-if="column.meta"> </td> </t> - <th t-if="options.selectable" class="oe-record-selector" width="1"> + <th t-if="options.selectable" class="oe_list_record_selector" width="1"> <t t-set="checked" t-value="options.select_view_id == record.get('id') ? 'checked' : null"/> <input t-if="options.radio" type="radio" name="radiogroup" t-att-checked="checked"/> <input t-if="!options.radio" type="checkbox" name="radiogroup" t-att-checked="checked"/> </th> <t t-foreach="columns" t-as="column"> - <t t-set="align" t-value="column.type === 'integer' or column.type == 'float'"/> - <td t-if="!column.meta and column.invisible !== '1'" t-att-title="column.help" - t-att-class="'oe-field-cell' + (align ? ' oe_number' : '') - + (column.tag === 'button' ? ' oe-button' : '')" - t-att-data-field="column.id"> - <t t-raw="render_cell(record, column)"/> - </td> + <t t-set="number" t-value="column.type === 'integer' or column.type == 'float'"/> + <t t-set="modifiers" t-value="column.modifiers_for(asData)"/> + <td t-if="!column.meta and column.invisible !== '1' and !column.reorderable" t-att-title="column.help" + t-attf-class="oe_list_field_cell oe_list_field_#{column.widget or column.type} #{number ? 'oe_number' : ''} #{column.tag === 'button' ? 'oe-button' : ''} #{modifiers.readonly ? 'oe_readonly' : ''} #{modifiers.required ? 'oe_required' : ''}" + t-att-data-field="column.id" + ><t t-raw="render_cell(record, column)"/></td> </t> - <td t-if="options.deletable" class='oe-record-delete' width="1"> + <td t-if="options.deletable" class='oe_list_record_delete' width="13px"> <button type="button" name="delete" class="oe_i">d</button> </td> </tr> - -<t t-name="ListView.row.save"> - <td> - <button class='oe_i oe-edit-row-save' type='button' name='save'>S</button> - </td> +<t t-extend="ListView.buttons"> + <t t-jquery="button.oe_list_add" t-operation="after"> + <button class="oe_button oe_list_save oe_highlight" + type="button">Save</button> + <span class="oe_alternative"> + <span class="oe_fade">or</span> + <a href="#" class="oe_bold oe_list_discard">Discard</a> + </span> + </t> +</t> +<button t-name="ListView.row.button" type="button" + t-att-title="widget.string" t-att-disabled="disabled || undefined" + t-att-class="disabled ? 'oe_list_button_disabled' : undefined" + ><img t-attf-src="#{prefix}/web/static/src/img/icons/#{widget.icon}.png" + t-att-alt="widget.string" /></button> +<t t-extend="ListView.row"> + <!-- adds back padding to row being rendered after edition, if necessary + (if not deletable add back padding), otherwise the row being added is + missing columns + --> + <t t-jquery="> :last" t-operation="after"> + <td t-if="edited and !options.deletable" class="oe-listview-padding"/> + </t> </t> <t t-name="FormView"> @@ -707,18 +728,24 @@ <div t-name="FormView.buttons" class="oe_form_buttons"> <t t-if="widget.options.action_buttons !== false"> <span class="oe_form_buttons_view"> - <button type="button" class="oe_button oe_form_button_edit">Edit</button> - <button type="button" class="oe_button oe_form_button_create">Create</button> + <!-- required for the bounce effect on button --> + <div t-if="widget.is_action_enabled('edit')" style="display: inline-block;"> + <button type="button" class="oe_button oe_form_button_edit" accesskey="E">Edit</button> + </div> + <button t-if="widget.is_action_enabled('create')" + type="button" class="oe_button oe_form_button_create" accesskey="C">Create</button> </span> <span class="oe_form_buttons_edit"> - <button type="button" class="oe_button oe_form_button_save">Save</button> <span class="oe_fade">or</span> <a href="#" class="oe_bold oe_form_button_cancel">Discard</a> + <button type="button" class="oe_button oe_form_button_save oe_highlight" accesskey="S">Save</button> + <span class="oe_fade">or</span> + <a href="#" class="oe_bold oe_form_button_cancel" accesskey="D">Discard</a> </span> </t> </div> <t t-name="FormView.pager"> <div class="oe_form_pager"> <t t-if="widget.options.pager !== false" t-call="ViewPager"> - <span class="oe_pager_index">0</span><span class="oe_pager_separator"> / </span><span class="oe_pager_count">0</span> + <span class="oe_form_pager_state"></span> </t> </div> </t> @@ -791,13 +818,13 @@ </t> <t t-name="FormRenderingGroup"> <t t-if="string" t-call="FormRenderingSeparator"/> - <table border="0" cellpadding="0" cellspacing="0" width="100%" t-attf-class="oe_form_group #{classnames}"/> + <table border="0" cellpadding="0" cellspacing="0" t-attf-class="oe_form_group #{classnames}"/> </t> <t t-name="FormRenderingNotebook"> - <div> - <ul t-attf-class="oe_form_notebook #{classnames}"> + <div class="oe_clear"> + <ul t-attf-class="oe_notebook #{classnames}"> <li t-foreach="pages" t-as="page" t-att-modifiers="page.modifiers"> - <a t-attf-href="##{page.id}"> + <a t-attf-href="##{page.id}" t-att-accesskey="page.accesskey"> <t t-esc="page.string"/> </a> </li> @@ -805,11 +832,11 @@ </div> </t> <t t-name="FormRenderingNotebookPage"> - <div t-attf-class="oe_form_notebook_page #{classnames}" t-att-id="id"> + <div t-attf-class="oe_notebook_page #{classnames}" t-att-id="id"> </div> </t> <t t-name="FormRenderingSeparator"> - <div t-attf-class="oe_horizontal_separator #{classnames}"> + <div t-attf-class="oe_horizontal_separator oe_clear #{classnames}"> <t t-esc="string"/> </div> </t> @@ -888,30 +915,29 @@ </ul> </t> <t t-name="FieldChar"> - <span t-att-class="'oe_form_field '+widget.widget_class"> + <span t-att-class="'oe_form_field '+widget.widget_class" t-att-style="widget.node.attrs.style"> <t t-if="!widget.get('effective_readonly')"> <input t-att-type="widget.password ? 'password' : 'text'" t-att-id="widget.id_for_label" t-att-tabindex="widget.node.attrs.tabindex" t-att-autofocus="widget.node.attrs.autofocus" t-att-placeholder="widget.node.attrs.placeholder" + t-att-maxlength="widget.field.size" /><img class="oe_field_translate oe_input_icon" t-if="widget.field.translate" t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0"/> </t> </span> </t> <t t-name="FieldEmail"> - <span class="oe_form_field oe_form_field_email oe_form_field_with_button"> - <a t-if="widget.get('effective_readonly')" href="#" class="oe_form_uri"/> + <span class="oe_form_field oe_form_field_email" t-att-style="widget.node.attrs.style"> + <a t-if="widget.get('effective_readonly')" href="#" class="oe_form_uri" target="_blank"/> <t t-if="!widget.get('effective_readonly')"> - <button class="oe_button" tabindex="-1" type="button" title="Send an e-mail with your default e-mail client"> - <img t-att-src='_s + "/web/static/src/img/icons/terp-mail-message-new.png"'/> - </button> <div> <input type="text" t-att-id="widget.id_for_label" t-att-tabindex="widget.node.attrs.tabindex" t-att-autofocus="widget.node.attrs.autofocus" t-att-placeholder="widget.node.attrs.placeholder" + t-att-maxlength="widget.field.size" /> </div> </t> @@ -921,23 +947,28 @@ <t t-jquery="span:first"> this.removeClass('oe_form_field_email').addClass('oe_form_field_url'); </t> - <t t-jquery="button.oe_button" t-operation="replace"> - <button class="oe_button" tabindex="-1" type="button" title="Open this resource"> - <img t-att-src='_s + "/web/static/src/img/icons/gtk-ok.png"'/> - </button> - </t> </t> <t t-name="FieldText"> - <div class="oe_form_field oe_form_field_text"> + <div class="oe_form_field oe_form_field_text" t-att-style="widget.node.attrs.style"> <textarea rows="6" t-att-name="widget.name" class="field_text" t-att-tabindex="widget.node.attrs.tabindex" t-att-autofocus="widget.node.attrs.autofocus" - t-att-placeholder="widget.node.attrs.placeholder" - ></textarea> - <img class="oe_field_translate oe_input_icon" t-if="widget.field.translate" - t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0"/> + t-att-placeholder="! widget.get('effective_readonly') ? widget.node.attrs.placeholder : ''" + t-att-maxlength="widget.field.size" + ></textarea><img class="oe_field_translate oe_input_icon" + t-if="widget.field.translate and !widget.get('effective_readonly')" + t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0" + /> + </div> +</t> +<t t-name="FieldTextHtml"> + <div t-att-class="'oe_form_field oe_form_field_html' + (widget.get('effective_readonly') ? ' oe_form_embedded_html' : '')" + t-att-style="widget.node.attrs.style"> + <t t-if="! widget.get('effective_readonly')"> + <textarea/> + </t> </div> </t> <t t-name="web.datepicker"> @@ -953,13 +984,13 @@ </span> </t> <t t-name="FieldDate"> - <span class="oe_form_field oe_datepicker_root oe_form_field_date"/> + <span class="oe_form_field oe_datepicker_root oe_form_field_date" t-att-style="widget.node.attrs.style"/> </t> <t t-name="FieldDatetime"> - <span class="oe_form_field oe_datepicker_root oe_form_field_datetime"/> + <span class="oe_form_field oe_datepicker_root oe_form_field_datetime" t-att-style="widget.node.attrs.style"/> </t> <t t-name="FieldSelection"> - <span class="oe_form_field oe_form_field_selection"> + <span class="oe_form_field oe_form_field_selection" t-att-style="widget.node.attrs.style"> <select t-if="!widget.get('effective_readonly')" t-att-name="widget.name" t-att-tabindex="widget.node.attrs.tabindex" @@ -972,12 +1003,15 @@ </span> </t> <t t-name="FieldMany2One"> - <span class="oe_form_field oe_form_field_many2one oe_form_field_with_button"> - <a t-if="widget.get('effective_readonly')" href="#" class="oe_form_uri"/> + <span class="oe_form_field oe_form_field_many2one oe_form_field_with_button" t-att-style="widget.node.attrs.style"> + <t t-if="widget.get('effective_readonly')"> + <a t-if="! widget.options.no_open" href="#" class="oe_form_uri"/> + <span t-if="widget.options.no_open" href="#" class="oe_form_uri"/> + <span class="oe_form_m2o_follow"/> + </t> <t t-if="!widget.get('effective_readonly')"> - <button class="oe_button oe-m2o-cm-button" title="Open Resource"> - <img t-att-src='_s + "/web/static/src/img/icons/terp-folder-yellow.png"'/> - </button> + <a t-if="! widget.options.no_open" href="#" tabindex="-1" + class="oe_m2o_cm_button oe_e">/</a> <div> <input type="text" t-att-id="widget.id_for_label" @@ -985,7 +1019,7 @@ t-att-autofocus="widget.node.attrs.autofocus" t-att-placeholder="widget.node.attrs.placeholder" /> - <span class="oe-m2o-drop-down-button"> + <span class="oe_m2o_drop_down_button"> <img t-att-src='_s + "/web/static/src/img/down-arrow.png"'/> </span> </div> @@ -993,23 +1027,24 @@ </span> </t> <t t-name="FieldMany2ManyTags"> - <div class="oe_form_field oe_form_field_many2manytags"> + <div class="oe_form_field oe_tags" t-att-style="widget.node.attrs.style"> <t t-if="! widget.get('effective_readonly')"> - <textarea rows="1" style="width: 100%"></textarea> + <textarea rows="1" style="width: 100%" + t-att-placeholder="widget.node.attrs.placeholder"></textarea> </t> </div> </t> -<t t-name="FieldMany2ManyTags.box"> +<t t-name="FieldMany2ManyTag"> <t t-set="i" t-value="0"/> <t t-foreach="elements" t-as="el"> - <span class="oe_form_field_many2manytags_box" t-att-data-index="i"> + <span class="oe_tag" t-att-data-index="i"> <t t-esc="el[1]"/> </span> <t t-set="i" t-value="i + 1"/> </t> </t> <t t-name="FieldReference"> - <table class="oe_form_field oe_form_field_reference oe_form" border="0" cellpadding="0" cellspacing="0"> + <table class="oe_form_field oe_form_field_reference" border="0" cellpadding="0" cellspacing="0" t-att-style="widget.node.attrs.style"> <tr> <td t-attf-class="oe_form_frame_cell oe_form_selection"> <span t-attf-class="oe_form_view_reference_selection"/> @@ -1021,7 +1056,7 @@ </table> </t> <t t-name="FieldBoolean"> - <span class="oe_form_field oe_form_field_boolean"> + <span class="oe_form_field oe_form_field_boolean" t-att-style="widget.node.attrs.style"> <input type="checkbox" t-att-id="widget.id_for_label" t-att-name="widget.name" @@ -1031,47 +1066,33 @@ </span> </t> <t t-name="FieldProgressBar"> - <span class="oe_form_field oe_form_field_progressbar" t-opentag="true"> + <span class="oe_form_field oe_form_field_progressbar" t-att-style="widget.node.attrs.style"> <span></span> </span> </t> +<t t-name="FieldStatus"> + <ul t-att-class="'oe_form_field_status ' + (widget.options.clickable ? 'oe_form_status_clickable' : 'oe_form_status')" t-att-style="widget.node.attrs.style"/> +</t> <t t-name="FieldStatus.content"> - <ul class="oe_form_steps"> - <t t-set="size" t-value="widget.to_show.length"/> - <t t-foreach="_.range(size)" t-as="i"> - <li t-att-class="widget.to_show[i][0] === widget.selected_value ? 'oe_form_steps_active' : ''"> - <span><t t-esc="widget.to_show[i][1]"/></span> - <img t-att-src='_s + "/web/static/src/img/form_steps.png"' class="oe_form_steps_arrow" t-if="i < size - 1"/> - </li> - </t> - </ul> - <div class="oe_clear"/> + <t t-foreach="widget.selection" t-as="i"> + <li t-att-class="i[0] === widget.selected_value ? 'oe_active' : ''" t-att-data-id="i[0]"> + <span class="label"><t t-esc="i[1]"/></span> + <!-- are you mit ? --> + <span class="arrow"><span></span></span> + </li> + </t> </t> <t t-name="FieldBinaryImage"> - <span class="oe_form_field oe_form_field_image"> - <div class="oe_form_field_image_controls"> - <div class="oe-binary-file-set"> - <form class="oe-binary-form" t-att-target="widget.iframe" - method="post" enctype="multipart/form-data" action="/web/binary/upload"> - <input type="hidden" name="session_id" value=""/> - <input type="hidden" name="callback" t-att-value="widget.iframe"/> - <button class="oe_button" type="button" title="Set Image"> - <img t-att-src='_s + "/web/static/src/img/icons/STOCK_DIRECTORY.png"'/> - </button> - <input type="file" class="oe-binary-file" name="ufile" - t-att-tabindex="widget.node.attrs.tabindex" - t-att-autofocus="widget.node.attrs.autofocus" - /> - </form> - </div> - <button class="oe_button oe-binary-file-clear" type="button" title="Clear"> - <img t-att-src='_s + "/web/static/src/img/icons/STOCK_MISSING_IMAGE.png"'/> - </button> - <div class="oe-binary-progress" style="display: none"> + <span class="oe_form_field oe_form_field_image" t-att-style="widget.node.attrs.style"> + <div class="oe_form_field_image_controls oe_edit_only"> + <t t-call="HiddenInputFile"> + <t t-set="fileupload_id" t-value="widget.fileupload_id"/> + Edit + </t> + <div class="oe_form_binary_progress" style="display: none"> <img t-att-src='_s + "/web/static/src/img/throbber.gif"' width="16" height="16"/> <b>Uploading ...</b> </div> - <iframe t-att-id="widget.iframe" t-att-name="widget.iframe" style="display: none"/> </div> </span> </t> @@ -1085,7 +1106,7 @@ </t> <t t-name="FieldBinaryFile"> <t t-if="!widget.get('effective_readonly')"> - <table class="oe_form_field oe_form_field_binary" cellpadding="0" cellspacing="0" border="0"> + <table class="oe_form_field oe_form_field_binary" cellpadding="0" cellspacing="0" border="0" t-att-style="widget.node.attrs.style"> <tr> <td> <input type="text" size="1" readonly="readonly" @@ -1095,31 +1116,29 @@ class="field_binary" /> </td> - <td class="oe-binary" nowrap="true"> + <td class="oe_form_binary" nowrap="true"> <table cellspacing="0" cellpadding="0" border="0"> <tr> <td> - <div class="oe-binary-file-set" style="width: 80px; height:22px;"> - <form class="oe-binary-form" t-att-target="widget.iframe" - method="post" enctype="multipart/form-data" action="/web/binary/upload"> - <input type="hidden" name="session_id" value=""/> - <input type="hidden" name="callback" t-att-value="widget.iframe"/> - <button class="oe_button oe_field_button" type="button" title="Set Image"> - <img t-att-src='_s + "/web/static/src/img/icons/STOCK_DIRECTORY.png"'/> - <span>Select</span> - </button> - <input type="file" class="oe-binary-file" name="ufile"/> - </form> - </div> + <t t-call="HiddenInputFile"> + <t t-set="fileupload_id" t-value="widget.fileupload_id"/> + <t t-set="fileupload_style">width: 83px;</t> + <button class="oe_button oe_field_button" type="button"> + <img t-att-src='_s + "/web/static/src/img/icons/STOCK_DIRECTORY.png"'/> + <span>Select</span> + </button> + </t> </td> <td> - <button class="oe_button oe-binary-file-save" type="button" title="Save As"> - <img t-att-src='_s + "/web/static/src/img/icons/gtk-save.png"'/> - <span>Save As</span> - </button> + <a class="oe_form_binary_file_save_data"> + <button class="oe_button oe_form_binary_file_save" type="button" title="Save As"> + <img t-att-src='_s + "/web/static/src/img/icons/gtk-save.png"'/> + <span>Save As</span> + </button> + </a> </td> <td> - <button class="oe_button oe-binary-file-clear" type="button" title="Clear"> + <button class="oe_button oe_form_binary_file_clear" type="button" title="Clear"> <img t-att-src='_s + "/web/static/src/img/icons/STOCK_MISSING_IMAGE.png"'/> <span>Clear</span> </button> @@ -1127,25 +1146,38 @@ </tr> </table> </td> - <td class="oe-binary-progress" style="display: none" nowrap="true"> + <td class="oe_form_binary_progress" style="display: none" nowrap="true"> <img t-att-src='_s + "/web/static/src/img/throbber.gif"' width="16" height="16"/> <b>Uploading ...</b> - <iframe t-att-id="widget.iframe" t-att-name="widget.iframe" style="display: none"/> </td> </tr> </table> </t> <t t-if="widget.get('effective_readonly')"> - <span> + <span t-att-style="widget.node.attrs.style"> <a href="javascript:void(0)" class="oe_form_uri"/> </span> </t> </t> +<t t-name="HiddenInputFile"> + <div t-attf-class="oe_hidden_input_file #{fileupload_class or ''}" t-att-style="fileupload_style"> + <form class="oe_form_binary_form" t-att-target="fileupload_id" + method="post" enctype="multipart/form-data" t-att-action="fileupload_action || '/web/binary/upload'"> + <input type="hidden" name="session_id" value=""/> + <input type="hidden" name="callback" t-att-value="fileupload_id"/> + <t t-raw="__content__"/> + <input type="file" class="oe_form_binary_file" name="ufile"/> + </form> + <iframe t-att-id="fileupload_id" t-att-name="fileupload_id" style="display: none"/> + </div> +</t> <t t-name="WidgetButton"> <button type="button" class="oe_button oe_form_button" + t-att-style="widget.node.attrs.style" t-att-tabindex="widget.node.attrs.tabindex" - t-att-autofocus="widget.node.attrs.autofocus"> - <img t-if="widget.node.attrs.icon" t-att-src="_s + '/web/static/src/img/icons/' + widget.node.attrs.icon + '.png'" width="16" height="16"/> + t-att-autofocus="widget.node.attrs.autofocus" + t-att-accesskey="widget.node.attrs.accesskey"> + <img t-if="widget.node.attrs.icon" t-att-src="_s + widget.node.attrs.icon" width="16" height="16"/> <span t-if="widget.string"><t t-esc="widget.string"/></span> </button> </t> @@ -1181,31 +1213,52 @@ </t> </t> -<t t-name="TranslateDialog"> - <table t-if="widget.view.translatable_fields" class="oe_form oe_translation_form" border="0" cellpadding="0" cellspacing="0" width="100%"> - <tr> - <td class="oe_form_separator" width="1%" nowrap="nowrap"> - <div class="oe_horizontal_separator">Field</div> - </td> - <th t-foreach="widget.languages" align="left"> - <div class="oe_horizontal_separator"><t t-esc="name"/></div> - </th> - </tr> - <tr t-foreach="widget.view.translatable_fields" t-as="field" t-att-data-field="field.name"> - <td class="oe_form_group_cell" width="1%" nowrap="nowrap"> - <label class="oe_label"><t t-esc="field.node.attrs.string"/>:</label> - </td> - <td t-foreach="widget.languages" t-as="lg" class="oe_form_group_cell"> - <input t-if="field.type == 'char'" type="text" t-attf-name="#{lg.code}-#{field.name}" value="" data-value="" class="oe_trad_field" style="width: 100%"/> - <textarea t-if="field.type == 'text'" t-attf-name="#{lg.code}-#{field.name}" data-value="" class="oe_trad_field" style="width: 100%"></textarea> - </td> - </tr> - </table> +<t t-name="AbstractFormPopup.render"> + <div> + <table style="width:100%"> + <tr style="width:100%"> + <td style="width:100%"> + <div class="oe_popup_search" style="width:100%"></div> + </td> + </tr> + <tr style="width:100%"> + <td style="width:100%"> + <div class="oe_popup_list" style="width:100%"></div> + </td> + </tr> + </table> + <div class="oe_popup_form" style="width:100%"></div> + </div> +</t> +<t t-name="SelectCreatePopup.search.buttons"> + <t t-if="! widget.options.disable_multiple_selection"> + <button type="button" class="oe_button oe_selectcreatepopup-search-select" disabled="disabled">Select</button> + or + </t> + <a class="oe_button oe_selectcreatepopup-search-close oe_bold oe_form_button_cancel" href="javascript:void(0)">Cancel</a> +</t> +<t t-name="AbstractFormPopup.buttons"> + <t t-if="! readonly"> + <t t-if="! multi_select"> + <button type="button" class="oe_button oe_abstractformpopup-form-save oe_highlight">Save</button> + </t> + <t t-if="multi_select"> + <button type="button" class="oe_button oe_abstractformpopup-form-save oe_highlight">Save & Close</button> + <button type="button" class="oe_button oe_abstractformpopup-form-save-new oe_highlight">Save & New</button> + </t> + or + </t> + <a class="oe_button oe_abstractformpopup-form-close oe_bold oe_form_button_cancel" href="javascript:void(0)"> + <t t-if="! readonly"> + Discard + </t> + <t t-if="readonly"> + Close + </t> + </a> </t> <t t-name="One2Many.viewmanager" t-extend="ViewManager"> - <t t-jquery="span.oe_view_title_text" t-operation="replace"/> - <t t-jquery=".oe-view-manager-header"> this.attr('t-if', 'views.length != 1'); </t> @@ -1233,8 +1286,10 @@ <div t-name="SearchView" class="oe_searchview"> <div class="oe_searchview_facets"/> <div class="oe_searchview_clear"/> - <div class="oe_searchview_unfold_drawer"/> + <div class="oe_searchview_unfold_drawer" title="Advanced Search..."/> <div class="oe_searchview_drawer"/> + <button type="button" class="oe_searchview_search" + title="Search Again">Search</button> </div> <div t-name="SearchView.InputView" @@ -1242,7 +1297,7 @@ contenteditable="true"/> <!-- tabindex: makes div focusable --> <div t-name="SearchView.FacetView" - class="oe_searchview_facet" + class="oe_tag oe_tag_dark oe_searchview_facet" tabindex="0" ><span class="oe_facet_remove">x</span ><span class="oe_facet_category oe_i" t-if="widget.model.has('icon')"> @@ -1255,9 +1310,8 @@ <span t-name="SearchView.FacetView.Value" class="oe_facet_value"> <t t-esc="widget.model.get('label')"/> </span> - <t t-name="SearchView.managed-filters"> - <option class="oe-filters-title" value="">Filters</option> + <option class="oe_search_filters_title" value="">Filters</option> <optgroup label="-- Filters --"> <t t-foreach="filters" t-as="filter"> <option t-attf-value="get:#{filter_index}" @@ -1270,7 +1324,6 @@ <optgroup label="-- Actions --"> <option value="advanced_filter">Add Advanced Filter</option> <option value="save_filter">Save Filter</option> - <option value="add_to_dashboard">Add to Dashboard</option> <option value="manage_filters">Manage Filters</option> </optgroup> </t> @@ -1281,18 +1334,8 @@ <p>(Any existing filter with the same name will be replaced)</p> </div> </t> -<t t-name="SearchView.add_to_dashboard"> - <div class="oe_form"> - <p><b>Select Dashboard to add this filter to:</b></p> - <select style="width: 100%; margin-right: 1em;"> - <option t-foreach="dashboards" t-as="menu" t-att-value="menu.id" t-att-selected="(menu.id == selected_menu_id) || undefined"><t t-esc="menu.name"/></option> - </select> - <p><b>Title of new Dashboard item:</b></p> - <input type="text" style="width: 100%; margin-right: 1em;"/> - </div> -</t> <t t-name="SearchView.render_lines"> - <table class="oe-searchview-render-line" border="0" cellspacing="0" cellpadding="0" + <table class="oe_search_render_line" border="0" cellspacing="0" cellpadding="0" t-foreach="lines" t-as="line"> <tr> <td t-foreach="line" t-as="widget" class="oe_searchview_field"> @@ -1409,10 +1452,10 @@ </t> </t> </t> -<div t-name="SearchView.Filters" class="oe_searchview_section"> +<div t-name="SearchView.Filters" class="oe_searchview_filters oe_searchview_section"> </div> -<div t-name="SearchView.CustomFilters" class="oe_searchview_section"> +<div t-name="SearchView.CustomFilters" class="oe_searchview_custom oe_searchview_section"> <div> <h3><span class="oe_i">M</span> Custom Filters</h3> <ul class="oe_searchview_custom_list"/> @@ -1429,6 +1472,7 @@ <div> </div> </div> + <div t-name="SearchView.advanced" class="oe_searchview_advanced"> <h4>Advanced Search</h4> <form> @@ -1443,8 +1487,7 @@ <li> <select class="searchview_extended_prop_field"> <t t-foreach="widget.attrs.fields" t-as="field"> - <option t-if="typeof field.store === 'undefined' || field.store || field.fnct_search" - t-att="{'selected': field === widget.attrs.selected ? 'selected' : null}" + <option t-att="{'selected': field === widget.attrs.selected ? 'selected' : null}" t-att-value="field.name"> <t t-esc="field.string"/> </option> @@ -1474,115 +1517,11 @@ </t> </select> </t> -<t t-name="AbstractFormPopup.render"> - <div> - <table style="width:100%"> - <tr style="width:100%"> - <td style="width:100%"> - <div class="oe-select-create-popup-view-search" style="width:100%"></div> - </td> - </tr> - <tr style="width:100%"> - <td style="width:100%"> - <div class="oe-select-create-popup-view-list" style="width:100%"></div> - </td> - </tr> - </table> - <div class="oe-form-view-popup-form-placeholder oe-select-create-popup-view-form" style="width:100%"></div> - </div> -</t> -<t t-name="SelectCreatePopup.search.buttons"> - <button type="button" class="oe_button oe_selectcreatepopup-search-select" disabled="disabled">Select</button> - <button type="button" class="oe_button oe_selectcreatepopup-search-close">Cancel</button> -</t> -<t t-name="AbstractFormPopup.buttons"> - <t t-if="! multi_select"> - <button type="button" class="oe_button oe_abstractformpopup-form-save">Save</button> - </t> - <t t-if="multi_select"> - <button type="button" class="oe_button oe_abstractformpopup-form-save-new">Save & New</button> - <button type="button" class="oe_button oe_abstractformpopup-form-save">Save & Close</button> - </t> - <button type="button" class="oe_button oe_abstractformpopup-form-close">Cancel</button> -</t> -<t t-extend="ListView.row"> - <!-- adds back padding to row being rendered after edition, if necessary - (if not deletable add back padding), otherwise the row being added is - missing columns - --> - <t t-jquery="> :last" t-operation="after"> - <td t-if="edited and !options.deletable" class="oe-listview-padding"/> - </t> -</t> - -<t t-name="view_editor"> - <table class="oe_view_editor"> - <t t-call="view_editor.row"/> - </table> -</t> -<t t-name="view_editor.row"> - <tr t-att-id="'viewedit-' + rec.id" t-att-level="rec.level" t-foreach="data" t-as="rec"> - <td width="90%"> - <table class="oe_view_editor_field"> - <tr> - <td width="16px" t-att-style="'background-position: ' + 20*rec.level + 'px; padding-left: ' + 20*rec.level + 'px'"> - <img t-if="rec.child_id.length" t-att-id="'parentimg-' + rec.id" - src="/web/static/src/img/collapse.gif" width="16" height="16" border="0"/> - </td> - <td style="cursor: pointer;"> - <a style="text-decoration:none" href="javascript:void(0);"> - <t t-esc="rec.name"/> - </a> - </td> - </tr> - </table> - </td> - <td width="2%"> - <img t-if="rec.att_list.length" - id="side-add" src="/web/static/src/img/icons/gtk-add.png" style="cursor: pointer;"/> - </td> - <td width="2%"> - <img id="side-remove" src="/web/static/src/img/icons/gtk-remove.png" style="cursor: pointer;"/> - </td> - <td width="2%"> - <img t-if="rec.att_list.length and !_.include(no_properties, rec.att_list[0])" - id="side-edit" src="/web/static/src/img/icons/gtk-edit.png" style="cursor: pointer;"/> - </td> - <td width="2%"> - <img t-if="rec.att_list.length" - id="side-up" src="/web/static/src/img/icons/gtk-go-up.png" style="cursor: pointer;"/> - </td> - <td width="2%"> - <img t-if="rec.att_list.length" - id="side-down" src="/web/static/src/img/icons/gtk-go-down.png" style="cursor: pointer;"/> - </td> - <t t-if="rec.child_id.length"> - <t t-set="data" t-value="rec.child_id"/> - <t t-call="view_editor.row"/> - </t> - </tr> -</t> -<t t-name="vieweditor_char"> - <input type="text" t-att-id="widget.name" class="field_char" size="50"/> -</t> -<t t-name="vieweditor_selection"> - <select t-att-id="widget.name" > - <t t-if="widget.selection" t-foreach="widget.selection" t-as="option"> - <option - t-att-value="typeof option === 'object' ? option[0] : option"> - <t t-esc="typeof option === 'object' ? option[1] : option"/> - </option> - </t> - </select> -</t> -<t t-name="vieweditor_boolean"> - <input type="checkbox" t-att-id="widget.name"/> -</t> <t t-name="ExportView"> <a id="exportview" href="javascript: void(0)" style="text-decoration: none;color: #3D3D3D;">Export</a> </t> -<table t-name="ExportTreeView" class="oe-export" +<table t-name="ExportTreeView" class="oe_export" style="background-color: #F3F3F3;"> <tr> <td colspan="3"> @@ -1687,7 +1626,7 @@ </t> <t t-name="ImportDataView"> <form name="import_data" id="import_data" action="" method="post" enctype="multipart/form-data" - class="oe-import oe-import-no-result"> + class="oe_import oe_import_no_result"> <input type="hidden" name="session_id" t-att-value="widget.session.session_id"/> <h2 class="separator horizontal">1. Import a .CSV file</h2> <p>Select a .CSV file to import. If you need a sample of file to import, @@ -1697,9 +1636,9 @@ <label for="csvfile">CSV File:</label> <input type="file" id="csvfile" size="50" name="csvfile"/> </p> - <h2 class="separator horizontal oe-import-result">2. Check your file format</h2> - <div id="result" class="oe-import-result"></div> - <fieldset class="oe-closed oe-import-result"> + <h2 class="separator horizontal oe_import_result">2. Check your file format</h2> + <div id="result" class="oe_import_result"></div> + <fieldset class="oe_closed oe_import_result"> <legend>Import Options</legend> <table> <tr> @@ -1739,7 +1678,7 @@ </tr> <tr> <td t-foreach="records[0]" t-as="column"> - <input class="sel_fields" placeholder="--- Don't Import ---"/><span class="oe-m2o-drop-down-button"> + <input class="sel_fields" placeholder="--- Don't Import ---"/><span class="oe_m2o_drop_down_button"> <img t-att-src='_s + "/web/static/src/img/down-arrow.png"' /></span> </td> </tr> @@ -1759,5 +1698,22 @@ <div t-name="Many2ManyKanban.quick_create" class="oe_kanban_quick_create"> <input t-att-placeholder="_t('Type name to search')"/> </div> - +<t t-name="Throbber"> + <div> + <div class="oe_blockui_spin" style="height: 50px"> + </div> + <br /> + <div class="oe_throbber_message" style="color:white"></div> + </div> +</t> +<t t-name="M2ODialog"> + <div> + Name: <input class="oe_form_m2o_input_name" type="text"/> + </div> +</t> +<t t-name="M2ODialog.buttons"> + <button class="oe_form_m2o_qc_button oe_button oe_highlight">Quick Add</button> + <button class="oe_form_m2o_sc_button oe_button">Add All Info...</button> + <button class="oe_form_m2o_cancel_button oe_button">Cancel</button> +</t> </templates> diff --git a/addons/web/static/test/Widget.js b/addons/web/static/test/Widget.js new file mode 100644 index 00000000000..3e6cc262565 --- /dev/null +++ b/addons/web/static/test/Widget.js @@ -0,0 +1,239 @@ +$(document).ready(function () { + var $fix = $('#qunit-fixture'); + var mod = { + setup: function () { + instance = window.openerp.init([]); + window.openerp.web.corelib(instance); + + instance.web.qweb = new QWeb2.Engine(); + instance.web.qweb.add_template( + '<no>' + + '<t t-name="test.widget.template">' + + '<ol>' + + '<li t-foreach="5" t-as="counter" ' + + 't-attf-class="class-#{counter}">' + + '<input/>' + + '<t t-esc="counter"/>' + + '</li>' + + '</ol>' + + '</t>' + + '<t t-name="test.widget.template-value">' + + '<p><t t-esc="widget.value"/></p>' + + '</t>' + + '</no>'); + } + }; + var instance; + + module('Widget.proxy', mod); + test('(String)', function () { + var W = instance.web.Widget.extend({ + exec: function () { + this.executed = true; + } + }); + var w = new W; + var fn = w.proxy('exec'); + fn(); + ok(w.executed, 'should execute the named method in the right context'); + }); + test('(String)(*args)', function () { + var W = instance.web.Widget.extend({ + exec: function (arg) { + this.executed = arg; + } + }); + var w = new W; + var fn = w.proxy('exec'); + fn(42); + ok(w.executed, "should execute the named method in the right context"); + equal(w.executed, 42, "should be passed the proxy's arguments"); + }); + test('(String), include', function () { + // the proxy function should handle methods being changed on the class + // and should always proxy "by name", to the most recent one + var W = instance.web.Widget.extend({ + exec: function () { + this.executed = 1; + } + }); + var w = new W; + var fn = w.proxy('exec'); + W.include({ + exec: function () { this.executed = 2; } + }); + + fn(); + equal(w.executed, 2, "should be lazily resolved"); + }); + + test('(Function)', function () { + var w = new (instance.web.Widget.extend({ })); + + var fn = w.proxy(function () { this.executed = true; }); + fn(); + ok(w.executed, "should set the function's context (like Function#bind)"); + }); + test('(Function)(*args)', function () { + var w = new (instance.web.Widget.extend({ })); + + var fn = w.proxy(function (arg) { this.executed = arg; }); + fn(42); + equal(w.executed, 42, "should be passed the proxy's arguments"); + }); + + module('Widget.renderElement', mod); + test('no template, default', function () { + var w = new (instance.web.Widget.extend({ })); + + var $original = w.$el; + ok($original, "should initially have a root element"); + w.renderElement(); + ok(w.$el, "should have generated a root element"); + ok($original !== w.$el, "should have generated a new root element"); + strictEqual(w.$el, w.$el, "should provide $el alias"); + ok(w.$el.is(w.el), "should provide raw DOM alias"); + + equal(w.el.nodeName, 'DIV', "should have generated the default element"); + equal(w.el.attributes.length, 0, "should not have generated any attribute"); + ok(_.isEmpty(w.$el.html(), "should not have generated any content")); + }); + test('no template, custom tag', function () { + var w = new (instance.web.Widget.extend({ + tagName: 'ul' + })); + w.renderElement(); + + equal(w.el.nodeName, 'UL', "should have generated the custom element tag"); + }); + test('no template, @id', function () { + var w = new (instance.web.Widget.extend({ + id: 'foo' + })); + w.renderElement(); + + equal(w.el.attributes.length, 1, "should have one attribute"); + equal(w.$el.attr('id'), 'foo', "should have generated the id attribute"); + equal(w.el.id, 'foo', "should also be available via property"); + }); + test('no template, @className', function () { + var w = new (instance.web.Widget.extend({ + className: 'oe_some_class' + })); + w.renderElement(); + + equal(w.el.className, 'oe_some_class', "should have the right property"); + equal(w.$el.attr('class'), 'oe_some_class', "should have the right attribute"); + }); + test('no template, bunch of attributes', function () { + var w = new (instance.web.Widget.extend({ + attributes: { + 'id': 'some_id', + 'class': 'some_class', + 'data-foo': 'data attribute', + 'clark': 'gable', + 'spoiler': 'snape kills dumbledore' + } + })); + w.renderElement(); + + equal(w.el.attributes.length, 5, "should have all the specified attributes"); + + equal(w.el.id, 'some_id'); + equal(w.$el.attr('id'), 'some_id'); + + equal(w.el.className, 'some_class'); + equal(w.$el.attr('class'), 'some_class'); + + equal(w.$el.attr('data-foo'), 'data attribute'); + equal(w.$el.data('foo'), 'data attribute'); + + equal(w.$el.attr('clark'), 'gable'); + equal(w.$el.attr('spoiler'), 'snape kills dumbledore'); + }); + + test('template', function () { + var w = new (instance.web.Widget.extend({ + template: 'test.widget.template' + })); + w.renderElement(); + + equal(w.el.nodeName, 'OL'); + equal(w.$el.children().length, 5); + equal(w.el.textContent, '01234'); + }); + + module('Widget.$', mod); + test('basic-alias', function () { + var w = new (instance.web.Widget.extend({ + template: 'test.widget.template' + })); + w.renderElement(); + + ok(w.$('li:eq(3)').is(w.$el.find('li:eq(3)')), + "should do the same thing as calling find on the widget root"); + }); + + module('Widget.events', mod); + test('delegate', function () { + var a = []; + var w = new (instance.web.Widget.extend({ + template: 'test.widget.template', + events: { + 'click': function () { + a[0] = true; + strictEqual(this, w, "should trigger events in widget") + }, + 'click li.class-3': 'class3', + 'change input': function () { a[2] = true; } + }, + class3: function () { a[1] = true; } + })); + w.renderElement(); + + w.$el.click(); + w.$('li:eq(3)').click(); + w.$('input:last').val('foo').change(); + + for(var i=0; i<3; ++i) { + ok(a[i], "should pass test " + i); + } + }); + test('undelegate', function () { + var clicked = false, newclicked = false; + var w = new (instance.web.Widget.extend({ + template: 'test.widget.template', + events: { 'click li': function () { clicked = true; } } + })); + w.renderElement(); + w.$el.on('click', 'li', function () { newclicked = true }); + + w.$('li').click(); + ok(clicked, "should trigger bound events"); + ok(newclicked, "should trigger bound events"); + clicked = newclicked = false; + + w.undelegateEvents(); + w.$('li').click(); + ok(!clicked, "undelegate should unbind events delegated"); + ok(newclicked, "undelegate should only unbind events it created"); + }); + + module('Widget.renderElement', mod); + asyncTest('repeated', 4, function () { + var w = new (instance.web.Widget.extend({ + template: 'test.widget.template-value' + })); + w.value = 42; + w.appendTo($fix) + .always(start) + .done(function () { + equal($fix.find('p').text(), '42', "DOM fixture should contain initial value"); + equal(w.$el.text(), '42', "should set initial value"); + w.value = 36; + w.renderElement(); + equal($fix.find('p').text(), '36', "DOM fixture should use new value"); + equal(w.$el.text(), '36', "should set new value"); + }); + }); +}); diff --git a/addons/web/static/test/evals.js b/addons/web/static/test/evals.js index 290a009f46d..757d98c5bcc 100644 --- a/addons/web/static/test/evals.js +++ b/addons/web/static/test/evals.js @@ -12,7 +12,7 @@ $(document).ready(function () { // Context n should have base evaluation context + all of contexts // 0..n-1 in its own evaluation context var active_id = 4; - var result = openerp.connection.test_eval_contexts([ + var result = openerp.session.test_eval_contexts([ { "__contexts": [ { @@ -56,7 +56,7 @@ $(document).ready(function () { }); }); test('non-literal_eval_contexts', function () { - var result = openerp.connection.test_eval_contexts([{ + var result = openerp.session.test_eval_contexts([{ "__ref": "compound_context", "__contexts": [ {"__ref": "context", "__debug": "{'type':parent.type}", @@ -133,4 +133,20 @@ $(document).ready(function () { }]); deepEqual(result, {type: 'out_invoice'}); }); + module('eval.domains', { + setup: function () { + openerp = window.openerp.testing.instanceFor('coresetup'); + window.openerp.web.dates(openerp); + } + }); + test('current_date', function () { + var current_date = openerp.web.date_to_str(new Date()); + var result = openerp.session.test_eval_domains( + [[],{"__ref":"domain","__debug":"[('name','>=',current_date),('name','<=',current_date)]","__id":"5dedcfc96648"}], + openerp.session.test_eval_get_context()); + deepEqual(result, [ + ['name', '>=', current_date], + ['name', '<=', current_date] + ]); + }) }); diff --git a/addons/web/static/test/fulltest.html b/addons/web/static/test/fulltest.html index a031325966a..2917b246b8d 100644 --- a/addons/web/static/test/fulltest.html +++ b/addons/web/static/test/fulltest.html @@ -2,7 +2,7 @@ <html style="height: 100%"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> - <title>OpenERP</title> + <title>OpenERP Web Full Stack</title> <link rel="shortcut icon" href="/web/static/src/img/favicon.ico" type="image/x-icon"/> <link rel="stylesheet" href="/web/static/lib/qunit/qunit.css"> @@ -12,7 +12,7 @@ <script src="/web/static/lib/underscore/underscore.string.js" type="text/javascript"></script> <!-- jquery --> - <script src="/web/static/lib/jquery/jquery-1.6.4.js"></script> + <script src="/web/static/lib/jquery/jquery-1.7.2.js"></script> <script src="/web/static/lib/jquery.ui/js/jquery-ui-1.8.17.custom.min.js"></script> <script src="/web/static/lib/jquery.ba-bbq/jquery.ba-bbq.js"></script> @@ -37,13 +37,7 @@ <script src="/web/static/src/js/test_support.js"></script> </head> <body id="oe" class="openerp"> - <h1 id="qunit-header"> - OpenERP Web Test Suite: javascript to XML-RPC (excluded) - </h1> - <h2 id="qunit-banner"></h2> - <div id="qunit-testrunner-toolbar"></div> - <h2 id="qunit-userAgent"></h2> - <ol id="qunit-tests"></ol> + <div id="qunit"></div> <div id="qunit-fixture"></div> <script type="text/javascript" src="/web/static/test/fulltest/dataset.js"></script> </body> diff --git a/addons/web/static/test/fulltest/dataset.js b/addons/web/static/test/fulltest/dataset.js index 9f7ab101869..d276f4e2494 100644 --- a/addons/web/static/test/fulltest/dataset.js +++ b/addons/web/static/test/fulltest/dataset.js @@ -7,7 +7,7 @@ $(document).ready(function () { t.module('Dataset shortcuts', 'data'); t.test('read_index', function (openerp) { var ds = new openerp.web.DataSet( - {session: openerp.connection}, 'some.model'); + {session: openerp.session}, 'some.model'); ds.ids = [10, 20, 30, 40, 50]; ds.index = 2; t.expect(ds.read_index(['a', 'b', 'c']), function (result) { @@ -24,7 +24,7 @@ $(document).ready(function () { }); t.test('default_get', function (openerp) { var ds = new openerp.web.DataSet( - {session: openerp.connection}, 'some.model', {foo: 'bar'}); + {session: openerp.session}, 'some.model', {foo: 'bar'}); t.expect(ds.default_get(['a', 'b', 'c']), function (result) { strictEqual(result.method, 'default_get'); strictEqual(result.model, 'some.model'); @@ -38,7 +38,7 @@ $(document).ready(function () { }); }); t.test('create', function (openerp) { - var ds = new openerp.web.DataSet({session: openerp.connection}, 'some.model'); + var ds = new openerp.web.DataSet({session: openerp.session}, 'some.model'); t.expect(ds.create({foo: 1, bar: 2}), function (r) { strictEqual(r.method, 'create'); @@ -51,7 +51,7 @@ $(document).ready(function () { }); }); t.test('write', function (openerp) { - var ds = new openerp.web.DataSet({session: openerp.connection}, 'mod'); + var ds = new openerp.web.DataSet({session: openerp.session}, 'mod'); t.expect(ds.write(42, {foo: 1}), function (r) { strictEqual(r.method, 'write'); @@ -69,7 +69,7 @@ $(document).ready(function () { // }); }); t.test('unlink', function (openerp) { - var ds = new openerp.web.DataSet({session: openerp.connection}, 'mod'); + var ds = new openerp.web.DataSet({session: openerp.session}, 'mod'); t.expect(ds.unlink([42]), function (r) { strictEqual(r.method, 'unlink'); @@ -81,7 +81,7 @@ $(document).ready(function () { }); }); t.test('call', function (openerp) { - var ds = new openerp.web.DataSet({session: openerp.connection}, 'mod'); + var ds = new openerp.web.DataSet({session: openerp.session}, 'mod'); t.expect(ds.call('frob', ['a', 'b', 42]), function (r) { strictEqual(r.method, 'frob'); @@ -91,8 +91,8 @@ $(document).ready(function () { ok(_.isEmpty(r.kwargs)); }); }); - t.test('name_get', function (openerp) { - var ds = new openerp.web.DataSet({session: openerp.connection}, 'mod'); + t.test('name_get').then(function (openerp) { + var ds = new openerp.web.DataSet({session: openerp.session}, 'mod'); t.expect(ds.name_get([1, 2], null), function (r) { strictEqual(r.method, 'name_get'); @@ -104,7 +104,7 @@ $(document).ready(function () { }); }); t.test('name_search, name', function (openerp) { - var ds = new openerp.web.DataSet({session: openerp.connection}, 'mod'); + var ds = new openerp.web.DataSet({session: openerp.session}, 'mod'); t.expect(ds.name_search('bob'), function (r) { strictEqual(r.method, 'name_search'); @@ -119,7 +119,7 @@ $(document).ready(function () { }); }); t.test('name_search, domain & operator', function (openerp) { - var ds = new openerp.web.DataSet({session: openerp.connection}, 'mod'); + var ds = new openerp.web.DataSet({session: openerp.session}, 'mod'); t.expect(ds.name_search(0, [['foo', '=', 3]], 'someop'), function (r) { strictEqual(r.method, 'name_search'); @@ -134,7 +134,7 @@ $(document).ready(function () { }); }); t.test('exec_workflow', function (openerp) { - var ds = new openerp.web.DataSet({session: openerp.connection}, 'mod'); + var ds = new openerp.web.DataSet({session: openerp.session}, 'mod'); t.expect(ds.exec_workflow(42, 'foo'), function (r) { strictEqual(r['service'], 'object'); strictEqual(r.method, 'exec_workflow'); @@ -147,7 +147,7 @@ $(document).ready(function () { }); t.test('DataSetSearch#read_slice', function (openerp) { - var ds = new openerp.web.DataSetSearch({session: openerp.connection}, 'mod'); + var ds = new openerp.web.DataSetSearch({session: openerp.session}, 'mod'); t.expect(ds.read_slice(['foo', 'bar'], { domain: [['foo', '>', 42], ['qux', '=', 'grault']], context: {peewee: 'herman'}, @@ -167,7 +167,7 @@ $(document).ready(function () { }); }); t.test('DataSetSearch#read_slice sorted', function (openerp) { - var ds = new openerp.web.DataSetSearch({session: openerp.connection}, 'mod'); + var ds = new openerp.web.DataSetSearch({session: openerp.session}, 'mod'); ds.sort('foo'); ds.sort('foo'); ds.sort('bar'); @@ -194,7 +194,7 @@ $(document).ready(function () { }); t.test('Dataset', function (openerp) { var ds = new openerp.web.DataSetSearch( - {session: openerp.connection}, 'mod'); + {session: openerp.session}, 'mod'); var c = new openerp.web.CompoundContext( {a: 'foo', b: 3, c: 5}, openerp.contexts[0]); t.expect(ds.read_slice(['foo', 'bar'], { @@ -220,7 +220,7 @@ $(document).ready(function () { parent: {model: 'qux'} }; var ds = new openerp.web.DataSet( - {session: openerp.connection}, 'mod', + {session: openerp.session}, 'mod', new openerp.web.CompoundContext({}) .set_eval_context(eval_context)); var domain = new openerp.web.CompoundDomain(openerp.domains[0]) diff --git a/addons/web/static/test/list-editable.js b/addons/web/static/test/list-editable.js new file mode 100644 index 00000000000..0388e56048d --- /dev/null +++ b/addons/web/static/test/list-editable.js @@ -0,0 +1,349 @@ +$(document).ready(function () { + var $fix = $('#qunit-fixture'); + + var instance; + var baseSetup = function () { + instance = openerp.testing.instanceFor('list_editable'); + + openerp.testing.loadTemplate(instance); + + openerp.testing.mockifyRPC(instance); + }; + + + /** + * + * @param {String} name + * @param {Object} [attrs] + * @param {String} [attrs.type="char"] + * @param {Boolean} [attrs.required] + * @param {Boolean} [attrs.invisible] + * @param {Boolean} [attrs.readonly] + * @return {Object} + */ + function field(name, attrs) { + attrs = attrs || {}; + attrs.name = name; + return _.defaults(attrs, { + type: 'char' + }); + } + + /** + * @param {Array} fields + * @return {Object} + */ + function makeFormView(fields) { + var fobj = {}; + _(fields).each(function (field) { + fobj[field.name] = { + type: field.type, + string: field.string + }; + }); + var children = _(fields).map(function (field) { + return { + tag: 'field', + attrs: { + name: field.name, + modifiers: JSON.stringify({ + required: field.required, + invisible: field.invisible, + readonly: field.readonly + }) + } + } + }); + return { + arch: { + tag: 'form', + attrs: { + version: '7.0', + 'class': 'oe_form_container' + }, + children: children + }, + fields: fobj + }; + } + + module('editor', { + setup: baseSetup + }); + asyncTest('base-state', 2, function () { + var e = new instance.web.list.Editor({ + dataset: {ids: []}, + edition_view: function () { + return makeFormView(); + } + }); + e.appendTo($fix) + .always(start) + .fail(function (error) { ok(false, error && error.message); }) + .done(function () { + ok(!e.is_editing(), "should not be editing"); + ok(e.form instanceof instance.web.FormView, + "should use default form type"); + }); + }); + asyncTest('toggle-edition-save', 4, function () { + instance.session.responses['/web/dataset/call_kw:create'] = function () { + return { result: 42 }; + }; + instance.session.responses['/web/dataset/call_kw:read'] = function () { + return { result: [{ + id: 42, + a: false, + b: false, + c: false + }]}; + }; + var e = new instance.web.list.Editor({ + dataset: new instance.web.DataSetSearch(), + prepends_on_create: function () { return false; }, + edition_view: function () { + return makeFormView([ field('a'), field('b'), field('c') ]); + } + }); + var counter = 0; + e.appendTo($fix) + .pipe(function () { + return e.edit({}, function () { + ++counter; + }); + }) + .pipe(function (form) { + ok(e.is_editing(), "should be editing"); + equal(counter, 3, "should have configured all fields"); + return e.save(); + }) + .always(start) + .fail(function (error) { ok(false, error && error.message); }) + .done(function (record) { + ok(!e.is_editing(), "should have stopped editing"); + equal(record.id, 42, "should have newly created id"); + }) + }); + asyncTest('toggle-edition-cancel', 2, function () { + instance.session.responses['/web/dataset/call_kw:create'] = function () { + return { result: 42 }; + }; + var e = new instance.web.list.Editor({ + dataset: new instance.web.DataSetSearch(), + prepends_on_create: function () { return false; }, + edition_view: function () { + return makeFormView([ field('a'), field('b'), field('c') ]); + } + }); + var counter = 0; + e.appendTo($fix) + .pipe(function () { + return e.edit({}, function () { + ++counter; + }); + }) + .pipe(function (form) { + return e.cancel(); + }) + .always(start) + .fail(function (error) { ok(false, error && error.message); }) + .done(function (record) { + ok(!e.is_editing(), "should have stopped editing"); + ok(!record.id, "should have no id"); + }) + }); + asyncTest('toggle-save-required', 2, function () { + instance.session.responses['/web/dataset/call_kw:create'] = function () { + return { result: 42 }; + }; + var e = new instance.web.list.Editor({ + do_warn: function () { + warnings++; + }, + dataset: new instance.web.DataSetSearch(), + prepends_on_create: function () { return false; }, + edition_view: function () { + return makeFormView([ + field('a', {required: true}), field('b'), field('c') ]); + } + }); + var counter = 0; + var warnings = 0; + e.appendTo($fix) + .pipe(function () { + return e.edit({}, function () { + ++counter; + }); + }) + .pipe(function (form) { + return e.save(); + }) + .always(start) + .done(function () { ok(false, "cancel should not succeed"); }) + .fail(function () { + equal(warnings, 1, "should have been warned"); + ok(e.is_editing(), "should have kept editing"); + }) + }); + + module('list-edition', { + setup: function () { + baseSetup(); + + var records = {}; + _.extend(instance.session.responses, { + '/web/view/load': function () { + return {result: { + type: 'tree', + fields: { + a: {type: 'char', string: "A"}, + b: {type: 'char', string: "B"}, + c: {type: 'char', string: "C"} + }, + arch: { + tag: 'tree', + attrs: {}, + children: [ + {tag: 'field', attrs: {name: 'a'}}, + {tag: 'field', attrs: {name: 'b'}}, + {tag: 'field', attrs: {name: 'c'}} + ] + } + }}; + }, + '/web/dataset/call_kw:create': function (params) { + records[42] = _.extend({}, params.params.args[0]); + return {result: 42}; + }, + '/web/dataset/call_kw:read': function (params) { + var id = params.params.args[0][0]; + if (id in records) { + return {result: [records[id]]}; + } + return {result: []}; + } + }) + } + }); + asyncTest('newrecord', 6, function () { + var got_defaults = false; + instance.session.responses['/web/dataset/call_kw:default_get'] = function (params) { + var fields = params.params.args[0]; + deepEqual( + fields, ['a', 'b', 'c'], + "should ask defaults for all fields"); + got_defaults = true; + return {result: { + a: "qux", + b: "quux" + }}; + }; + + var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]); + var l = new instance.web.ListView({}, ds, false, {editable: 'top'}); + + l.appendTo($fix) + .pipe(l.proxy('reload_content')) + .pipe(function () { + return l.start_edition(); + }) + .always(start) + .pipe(function () { + ok(got_defaults, "should have fetched default values for form"); + return l.save_edition(); + }) + .pipe(function (result) { + ok(result.created, "should yield newly created record"); + equal(result.record.get('a'), "qux", + "should have used default values"); + equal(result.record.get('b'), "quux", + "should have used default values"); + ok(!result.record.get('c'), + "should have no value if there was no default"); + }) + .fail(function (e) { ok(false, e && e.message || e); }); + }); + + module('list-edition-events', { + setup: function () { + baseSetup(); + _.extend(instance.session.responses, { + '/web/view/load': function () { + return {result: { + type: 'tree', + fields: { + a: {type: 'char', string: "A"}, + b: {type: 'char', string: "B"}, + c: {type: 'char', string: "C"} + }, + arch: { + tag: 'tree', + attrs: {}, + children: [ + {tag: 'field', attrs: {name: 'a'}}, + {tag: 'field', attrs: {name: 'b'}}, + {tag: 'field', attrs: {name: 'c'}} + ] + } + }}; + }, + '/web/dataset/call_kw:read': function (params) { + return {result: [{ + id: 1, + a: 'foo', + b: 'bar', + c: 'baz' + }]}; + } + }); + } + }); + asyncTest('edition events', 4, function () { + var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]); + var o = { + counter: 0, + onEvent: function (e) { this.counter++; } + }; + var l = new instance.web.ListView({}, ds, false, {editable: 'top'}); + l.on('edit:before edit:after', o, o.onEvent); + l.appendTo($fix) + .pipe(l.proxy('reload_content')) + .always(start) + .pipe(function () { + ok(l.options.editable, "should be editable"); + equal(o.counter, 0, "should have seen no event yet"); + return l.start_edition(l.records.get(1)); + }) + .pipe(function () { + ok(l.editor.is_editing(), "should be editing"); + equal(o.counter, 2, "should have seen two edition events"); + }) + .fail(function (e) { ok(false, e && e.message); }); + }); + + asyncTest('edition events: cancelling', 3, function () { + var edit_after = false; + var ds = new instance.web.DataSetStatic(null, 'demo', null, [1]); + var l = new instance.web.ListView({}, ds, false, {editable: 'top'}); + l.on('edit:before', {}, function (e) { + e.cancel = true; + }); + l.on('edit:after', {}, function () { + edit_after = true; + }); + l.appendTo($fix) + .pipe(l.proxy('reload_content')) + .always(start) + .pipe(function () { + ok(l.options.editable, "should be editable"); + return l.start_edition(); + }) + // cancelling an event rejects the deferred + .pipe($.Deferred().reject(), function () { + ok(!l.editor.is_editing(), "should not be editing"); + ok(!edit_after, "should not have fired the edit:after event"); + return $.when(); + }) + .fail(function (e) { ok(false, e && e.message || e); }); + }); +}); diff --git a/addons/web/static/test/list-utils.js b/addons/web/static/test/list-utils.js index 7890e71c6bc..6200ba788ce 100644 --- a/addons/web/static/test/list-utils.js +++ b/addons/web/static/test/list-utils.js @@ -133,7 +133,7 @@ $(document).ready(function () { strictEqual(changed, 1); }); - module('list-collections-degenerate', { + module('list-collections', { setup: function () { openerp = window.openerp.init([]); window.openerp.web.corelib(openerp); @@ -145,7 +145,7 @@ $(document).ready(function () { window.openerp.web.list(openerp); } }); - test('Fetch from collection', function () { + test('degenerate-fetch', function () { var c = new openerp.web.list.Collection(); strictEqual(c.length, 0); c.add({id: 1, value: 2}); @@ -163,7 +163,7 @@ $(document).ready(function () { strictEqual(r2.get('id'), 1); strictEqual(r2.get('value'), 2); }); - test('Add at index', function () { + test('degenerate-indexed-add', function () { var c = new openerp.web.list.Collection([ {id: 1, value: 5}, {id: 2, value: 10}, @@ -175,7 +175,7 @@ $(document).ready(function () { strictEqual(c.at(1).get('value'), 55); strictEqual(c.at(3).get('value'), 20); }); - test('Remove record', function () { + test('degenerate-remove', function () { var c = new openerp.web.list.Collection([ {id: 1, value: 5}, {id: 2, value: 10}, @@ -188,7 +188,7 @@ $(document).ready(function () { equal(c.get(2), undefined); strictEqual(c.at(1).get('value'), 20); }); - test('Remove unbind', function () { + test('degenerate-remove-bound', function () { var changed = false, c = new openerp.web.list.Collection([ {id: 1, value: 5} ]); c.bind('change', function () { changed = true; }); @@ -198,7 +198,7 @@ $(document).ready(function () { ok(!changed, 'removed records should not trigger events in their ' + 'parent collection'); }); - test('Reset', function () { + test('degenerate-reset', function () { var event, obj, c = new openerp.web.list.Collection([ {id: 1, value: 5}, {id: 2, value: 10}, @@ -218,7 +218,7 @@ $(document).ready(function () { strictEqual(c.length, 1); strictEqual(c.get(42).get('value'), 55); }); - test('Reset unbind', function () { + test('degenerate-reset-bound', function () { var changed = false, c = new openerp.web.list.Collection([ {id: 1, value: 5} ]); c.bind('change', function () { changed = true; }); @@ -229,7 +229,7 @@ $(document).ready(function () { 'parent collection'); }); - test('Events propagation', function () { + test('degenerate-propagations', function () { var values = []; var c = new openerp.web.list.Collection([ {id: 1, value: 5}, @@ -260,6 +260,82 @@ $(document).ready(function () { c.at(1).set('wealth', 5); strictEqual(total, 47); }); + test('degenerate-successor', function () { + var root = new openerp.web.list.Collection([ + {id: 1, value: 1}, + {id: 2, value: 2}, + {id: 3, value: 3}, + {id: 4, value: 5}, + {id: 5, value: 8} + ]); + + deepEqual(root.succ(root.at(2)).attributes, + root.at(3).attributes, + "should return the record at (index + 1) from the pivot"); + equal(root.succ(root.at(4)), null, + "should return null as successor to last record"); + deepEqual(root.succ(root.at(4), {wraparound: true}).attributes, + root.at(0).attributes, + "should return index 0 as successor to last record if" + + " wraparound is set"); + deepEqual(root.succ(root.at(2), {wraparound: true}).attributes, + root.at(3).attributes, + "wraparound should have no effect if not succ(last_record)"); + }); + test('successor', function () { + var root = new openerp.web.list.Collection(); + root.proxy('first').add([{id: 1, value: 1}, {id: 2, value: 2}]); + root.proxy('second').add([{id: 3, value: 3}, {id: 4, value: 5}]); + root.proxy('third').add([{id: 5, value: 8}, {id: 6, value: 13}]); + + deepEqual(root.succ(root.get(3)).attributes, + root.get(4).attributes, + "should get successor"); + equal(root.succ(root.get(4)), + null, + "successors do not cross collections"); + deepEqual(root.succ(root.get(4), {wraparound: true}).attributes, + root.get(3).attributes, + "should wraparound within a collection"); + }); + test('degenerate-predecessor', function () { + var root = new openerp.web.list.Collection([ + {id: 1, value: 1}, + {id: 2, value: 2}, + {id: 3, value: 3}, + {id: 4, value: 5}, + {id: 5, value: 8} + ]); + + deepEqual(root.pred(root.at(2)).attributes, + root.at(1).attributes, + "should return the record at (index - 1) from the pivot"); + equal(root.pred(root.at(0)), null, + "should return null as predecessor to first record"); + deepEqual(root.pred(root.at(0), {wraparound: true}).attributes, + root.at(4).attributes, + "should return last record as predecessor to first record" + + " if wraparound is set"); + deepEqual(root.pred(root.at(1), {wraparound: true}).attributes, + root.at(0).attributes, + "wraparound should have no effect if not pred(first_record)"); + }); + test('predecessor', function () { + var root = new openerp.web.list.Collection(); + root.proxy('first').add([{id: 1, value: 1}, {id: 2, value: 2}]); + root.proxy('second').add([{id: 3, value: 3}, {id: 4, value: 5}]); + root.proxy('third').add([{id: 5, value: 8}, {id: 6, value: 13}]); + + deepEqual(root.pred(root.get(4)).attributes, + root.get(3).attributes, + "should get predecessor"); + equal(root.pred(root.get(3)), + null, + "predecessor do not cross collections"); + deepEqual(root.pred(root.get(3), {wraparound: true}).attributes, + root.get(4).attributes, + "should wraparound within a collection"); + }); module('list-hofs', { setup: function () { @@ -338,4 +414,33 @@ $(document).ready(function () { ids, [1, 2, 3, 10, 20, 30], 'tree collections should be deeply iterated'); }); + + module("list-weirds", { + setup: function () { + openerp = window.openerp.init([]); + window.openerp.web.corelib(openerp); + window.openerp.web.coresetup(openerp); + window.openerp.web.chrome(openerp); + // views loader stuff + window.openerp.web.data(openerp); + window.openerp.web.views(openerp); + window.openerp.web.list(openerp); + } + }); + test('set-from-noid', function () { + var root = new openerp.web.list.Collection(); + root.add({v: 3}); + root.at(0).set('id', 42); + var record = root.get(42); + equal(root.length, 1); + equal(record.get('v'), 3, "should have fetched the original record"); + }); + test('set-from-previd', function () { + var root = new openerp.web.list.Collection(); + root.add({id: 1, v: 2}); + root.get(1).set('id', 42); + var record = root.get(42); + equal(root.length, 1); + equal(record.get('v'), 2, "should have fetched the original record"); + }); }); diff --git a/addons/web/static/test/registry.js b/addons/web/static/test/registry.js index da0cb952783..ca689a351e0 100644 --- a/addons/web/static/test/registry.js +++ b/addons/web/static/test/registry.js @@ -9,21 +9,6 @@ $(document).ready(function () { openerp.web.Foo2 = {}; } }); - test('key fetch', function () { - var reg = new openerp.web.Registry({ - foo: 'openerp.web.Foo', - bar: 'openerp.web.Bar', - quux: 'openerp.web.Quux' - }); - - strictEqual(reg.get_object('foo'), openerp.web.Foo); - raises(function () { reg.get_object('qux'); }, - openerp.web.KeyNotFound, - "Unknown keys should raise KeyNotFound"); - raises(function () { reg.get_object('quux'); }, - openerp.web.ObjectNotFound, - "Incorrect file paths should raise ObjectNotFound"); - }); test('key set', function () { var reg = new openerp.web.Registry(); diff --git a/addons/web/static/test/search.js b/addons/web/static/test/search.js index e5beab22498..6dc37f31d08 100644 --- a/addons/web/static/test/search.js +++ b/addons/web/static/test/search.js @@ -1,37 +1,8 @@ $(document).ready(function () { - var xhr = QWeb2.Engine.prototype.get_xhr(); - xhr.open('GET', '/web/static/src/xml/base.xml', false); - xhr.send(null); - var doc = xhr.responseXML; - - var noop = function () {}; - /** - * Make connection RPC responses mockable by setting keys on the - * Connection#responses object (key is the URL, value is the function to - * call with the RPC request payload) - * - * @param {openerp.web.Connection} connection connection instance to mockify - * @param {Object} [responses] url:function mapping to seed the mock connection - */ - var mockifyRPC = function (connection, responses) { - connection.responses = responses || {}; - connection.rpc_function = function (url, payload) { - if (!(url.url in this.responses)) { - return $.Deferred().reject({}, 'failed', _.str.sprintf("Url %s not found in mock responses", url.url)).promise(); - } - return $.when(this.responses[url.url](payload)); - }; - }; - var instance; module('query', { setup: function () { - instance = window.openerp.init([]); - window.openerp.web.corelib(instance); - window.openerp.web.coresetup(instance); - window.openerp.web.chrome(instance); - window.openerp.web.data(instance); - window.openerp.web.search(instance); + instance = openerp.testing.instanceFor('search'); } }); test('Adding a facet to the query creates a facet and a value', function () { @@ -167,16 +138,11 @@ $(document).ready(function () { module('defaults', { setup: function () { - instance = window.openerp.init([]); - window.openerp.web.corelib(instance); - window.openerp.web.coresetup(instance); - window.openerp.web.chrome(instance); - window.openerp.web.data(instance); - window.openerp.web.search(instance); + instance = openerp.testing.instanceFor('search'); - instance.web.qweb.add_template(doc); + openerp.testing.loadTemplate(instance); - mockifyRPC(instance.connection); + openerp.testing.mockifyRPC(instance); } }); @@ -198,8 +164,8 @@ $(document).ready(function () { instance.dummy = {}; instance.dummy.DummyWidget = instance.web.search.Field.extend( dummy_widget_attributes || {}); - if (!('/web/searchview/load' in instance.connection.responses)) { - instance.connection.responses['/web/searchview/load'] = function () { + if (!('/web/searchview/load' in instance.session.responses)) { + instance.session.responses['/web/searchview/load'] = function () { return {result: {fields_view: { type: 'search', fields: { @@ -220,10 +186,10 @@ $(document).ready(function () { }}}; }; } - instance.connection.responses['/web/searchview/get_filters'] = function () { + instance.session.responses['/web/searchview/get_filters'] = function () { return {result: []}; }; - instance.connection.responses['/web/searchview/fields_get'] = function () { + instance.session.responses['/web/searchview/fields_get'] = function () { return {result: {fields: { dummy: {type: 'char', string: 'Dummy'} }}}; @@ -355,7 +321,7 @@ $(document).ready(function () { {attrs: {name: 'dummy', string: 'Dummy'}}, {relation: 'dummy.model.name'}, view); - instance.connection.responses['/web/dataset/call_kw'] = function (req) { + instance.session.responses['/web/dataset/call_kw'] = function (req) { equal(req.params.method, 'name_get', "m2o should resolve default id"); equal(req.params.model, f.attrs.relation, @@ -391,7 +357,7 @@ $(document).ready(function () { {attrs: {name: 'dummy', string: 'Dummy'}}, {relation: 'dummy.model.name'}, view); - instance.connection.responses['/web/dataset/call_kw'] = function (req) { + instance.session.responses['/web/dataset/call_kw'] = function (req) { return {result: []}; }; f.facet_for_defaults({dummy: id}) @@ -404,18 +370,11 @@ $(document).ready(function () { module('completions', { setup: function () { - instance = window.openerp.init([]); - window.openerp.web.corelib(instance); - window.openerp.web.coresetup(instance); - window.openerp.web.chrome(instance); - window.openerp.web.data(instance); - // date complete - window.openerp.web.formats(instance); - window.openerp.web.search(instance); - - instance.web.qweb.add_template(doc); - - mockifyRPC(instance.connection); + instance = openerp.testing.instanceFor('search'); + + openerp.testing.loadTemplate(instance); + + openerp.testing.mockifyRPC(instance); } }); asyncTest('calling', 4, function () { @@ -432,10 +391,7 @@ $(document).ready(function () { } }); view.appendTo($('#qunit-fixture')) - .always(start) - .fail(function (error) { ok(false, error.message); }) .done(function () { - stop(); view.complete_global_search({term: "dum"}, function (completions) { start(); equal(completions.length, 1, "should have a single completion"); @@ -454,7 +410,11 @@ $(document).ready(function () { var completion = { label: "Dummy", facet: { - field: {get_domain: noop, get_context: noop, get_groupby: noop}, + field: { + get_domain: openerp.testing.noop, + get_context: openerp.testing.noop, + get_groupby: openerp.testing.noop + }, category: 'Dummy', values: [{label: 'dummy', value: 42}] } @@ -476,7 +436,11 @@ $(document).ready(function () { }); }); asyncTest('facet selection: new value existing facet', 3, function () { - var field = {get_domain: noop, get_context: noop, get_groupby: noop}; + var field = { + get_domain: openerp.testing.noop, + get_context: openerp.testing.noop, + get_groupby: openerp.testing.noop + }; var completion = { label: "Dummy", facet: { @@ -609,7 +573,7 @@ $(document).ready(function () { }); }); asyncTest("M2O", 15, function () { - instance.connection.responses['/web/dataset/call_kw'] = function (req) { + instance.session.responses['/web/dataset/call_kw'] = function (req) { equal(req.params.method, "name_search"); equal(req.params.model, "dummy.model"); deepEqual(req.params.args, []); @@ -643,7 +607,7 @@ $(document).ready(function () { }); }); asyncTest("M2O no match", 5, function () { - instance.connection.responses['/web/dataset/call_kw'] = function (req) { + instance.session.responses['/web/dataset/call_kw'] = function (req) { equal(req.params.method, "name_search"); equal(req.params.model, "dummy.model"); deepEqual(req.params.args, []); @@ -663,16 +627,11 @@ $(document).ready(function () { module('search-serialization', { setup: function () { - instance = window.openerp.init([]); - window.openerp.web.corelib(instance); - window.openerp.web.coresetup(instance); - window.openerp.web.chrome(instance); - window.openerp.web.data(instance); - window.openerp.web.search(instance); + instance = openerp.testing.instanceFor('search'); - instance.web.qweb.add_template(doc); + openerp.testing.loadTemplate(instance); - mockifyRPC(instance.connection); + openerp.testing.mockifyRPC(instance); } }); asyncTest('No facet, no call', 6, function () { @@ -843,6 +802,25 @@ $(document).ready(function () { deepEqual(f.get_domain(facet), [['foo', '=', 42]], "m2o should use identity if default domain"); + deepEqual(f.get_context(facet), {default_foo: 42}, + "m2o should use value as context default"); + }); + test("M2O default multiple values", function () { + var f = new instance.web.search.ManyToOneField( + {}, {name: 'foo'}, {inputs: []}); + var facet = new instance.web.search.Facet({ + field: f, + values: [ + {label: "Foo", value: 42}, + {label: "Bar", value: 36} + ] + }); + + deepEqual(f.get_domain(facet).__domains, + [['|'], [['foo', '=', 42]], [['foo', '=', 36]]], + "m2o should or multiple values"); + equal(f.get_context(facet), null, + "m2o should not have default context in case of multiple values"); }); test("M2O custom operator", function () { var f = new instance.web.search.ManyToOneField( @@ -854,6 +832,8 @@ $(document).ready(function () { deepEqual(f.get_domain(facet), [['foo', 'boos', 'Foo']], "m2o should use label with custom operators"); + deepEqual(f.get_context(facet), {default_foo: 42}, + "m2o should use value as context default"); }); test("M2O custom domain & context", function () { var f = new instance.web.search.ManyToOneField({attrs: { @@ -919,16 +899,11 @@ $(document).ready(function () { module('removal', { setup: function () { - instance = window.openerp.init([]); - window.openerp.web.corelib(instance); - window.openerp.web.coresetup(instance); - window.openerp.web.chrome(instance); - window.openerp.web.data(instance); - window.openerp.web.search(instance); + instance = openerp.testing.instanceFor('search'); - instance.web.qweb.add_template(doc); + openerp.testing.loadTemplate(instance); - mockifyRPC(instance.connection); + openerp.testing.mockifyRPC(instance); } }); asyncTest('clear button', function () { @@ -954,16 +929,11 @@ $(document).ready(function () { module('drawer', { setup: function () { - instance = window.openerp.init([]); - window.openerp.web.corelib(instance); - window.openerp.web.coresetup(instance); - window.openerp.web.chrome(instance); - window.openerp.web.data(instance); - window.openerp.web.search(instance); + instance = openerp.testing.instanceFor('search'); - instance.web.qweb.add_template(doc); + openerp.testing.loadTemplate(instance); - mockifyRPC(instance.connection); + openerp.testing.mockifyRPC(instance); } }); asyncTest('is-drawn', 2, function () { @@ -982,16 +952,11 @@ $(document).ready(function () { module('filters', { setup: function () { - instance = window.openerp.init([]); - window.openerp.web.corelib(instance); - window.openerp.web.coresetup(instance); - window.openerp.web.chrome(instance); - window.openerp.web.data(instance); - window.openerp.web.search(instance); + instance = openerp.testing.instanceFor('search'); - instance.web.qweb.add_template(doc); + openerp.testing.loadTemplate(instance); - mockifyRPC(instance.connection, { + openerp.testing.mockifyRPC(instance, { '/web/searchview/load': function () { // view with a single group of filters return {result: {fields_view: { @@ -1094,19 +1059,73 @@ $(document).ready(function () { }); }); + module('saved_filters', { + setup: function () { + instance = openerp.testing.instanceFor('search'); + + openerp.testing.loadTemplate(instance); + + openerp.testing.mockifyRPC(instance); + } + }); + asyncTest('checkboxing', 6, function () { + var view = makeSearchView(); + instance.session.responses['/web/searchview/get_filters'] = function () { + return {result: [{ + name: "filter name", + user_id: 42 + }]}; + }; + var $fix = $('#qunit-fixture'); + + view.appendTo($fix) + .always(start) + .fail(function (error) { ok(false, error.message); }) + .done(function () { + var $row = $fix.find('.oe_searchview_custom li:first').click(); + + ok($row.hasClass('oe_selected'), "should check/select the filter's row"); + ok($row.hasClass("oe_searchview_custom_private"), + "should have private filter note/class"); + equal(view.query.length, 1, "should have only one facet"); + var values = view.query.at(0).values; + equal(values.length, 1, + "should have only one value in the facet"); + equal(values.at(0).get('label'), 'filter name', + "displayed label should be the name of the filter"); + equal(values.at(0).get('value'), null, + "should have no value set"); + }) + }); + asyncTest('removal', 1, function () { + var view = makeSearchView(); + instance.session.responses['/web/searchview/get_filters'] = function () { + return {result: [{ + name: "filter name", + user_id: 42 + }]}; + }; + var $fix = $('#qunit-fixture'); + + view.appendTo($fix) + .always(start) + .fail(function (error) { ok(false, error.message); }) + .done(function () { + var $row = $fix.find('.oe_searchview_custom li:first').click(); + + view.query.remove(view.query.at(0)); + ok(!$row.hasClass('oe_selected'), + "should not be checked anymore"); + }) + }); + module('advanced', { setup: function () { - instance = window.openerp.init([]); - window.openerp.web.corelib(instance); - window.openerp.web.coresetup(instance); - window.openerp.web.chrome(instance); - window.openerp.web.data(instance); - window.openerp.web.formats(instance); - window.openerp.web.search(instance); + instance = openerp.testing.instanceFor('search'); - instance.web.qweb.add_template(doc); + openerp.testing.loadTemplate(instance); - mockifyRPC(instance.connection); + openerp.testing.mockifyRPC(instance); } }); asyncTest('single-advanced', 6, function () { diff --git a/addons/web/static/test/test.html b/addons/web/static/test/test.html index e352f63c17c..209723af5a0 100644 --- a/addons/web/static/test/test.html +++ b/addons/web/static/test/test.html @@ -2,7 +2,7 @@ <html style="height: 100%"> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8" /> - <title>OpenERP</title> + <title>OpenERP Web Test Suite</title> <link rel="shortcut icon" href="/web/static/src/img/favicon.ico" type="image/x-icon"/> <link rel="stylesheet" href="/web/static/lib/qunit/qunit.css"> @@ -13,7 +13,7 @@ <script src="/web/static/lib/backbone/backbone.js" type="text/javascript"></script> <!-- jquery --> - <script src="/web/static/lib/jquery/jquery-1.7.2b1.js"></script> + <script src="/web/static/lib/jquery/jquery-1.7.2.js"></script> <script src="/web/static/lib/jquery.ui/js/jquery-ui-1.8.17.custom.min.js"></script> <script src="/web/static/lib/jquery.ba-bbq/jquery.ba-bbq.js"></script> @@ -38,13 +38,15 @@ <script src="/web/static/src/js/search.js"></script> <script src="/web/static/src/js/view_form.js"></script> <script src="/web/static/src/js/view_list.js"></script> + <script src="/web/static/src/js/view_list_editable.js"></script> + + <script src="/web/static/test/testing.js"></script> + <script type="text/javascript"> + QUnit.config.testTimeout = 2000; + </script> </head> <body id="oe" class="openerp"> - <h1 id="qunit-header">OpenERP web Test Suite</h1> - <h2 id="qunit-banner"></h2> - <div id="qunit-testrunner-toolbar"></div> - <h2 id="qunit-userAgent"></h2> - <ol id="qunit-tests"></ol> + <div id="qunit"></div> <div id="qunit-fixture"></div> </body> <script type="text/javascript" src="/web/static/test/class.js"></script> @@ -55,4 +57,6 @@ <script type="text/javascript" src="/web/static/test/rpc.js"></script> <script type="text/javascript" src="/web/static/test/evals.js"></script> <script type="text/javascript" src="/web/static/test/search.js"></script> + <script type="text/javascript" src="/web/static/test/Widget.js"></script> + <script type="text/javascript" src="/web/static/test/list-editable.js"></script> </html> diff --git a/addons/web/static/test/testing.js b/addons/web/static/test/testing.js new file mode 100644 index 00000000000..1be8e94e74b --- /dev/null +++ b/addons/web/static/test/testing.js @@ -0,0 +1,97 @@ +// Test support structures and methods for OpenERP +openerp.testing = (function () { + var xhr = QWeb2.Engine.prototype.get_xhr(); + xhr.open('GET', '/web/static/src/xml/base.xml', false); + xhr.send(null); + var doc = xhr.responseXML; + + var dependencies = { + corelib: [], + coresetup: ['corelib'], + data: ['corelib', 'coresetup'], + dates: [], + formats: ['coresetup', 'dates'], + chrome: ['corelib', 'coresetup'], + views: ['corelib', 'coresetup', 'data', 'chrome'], + search: ['data', 'coresetup', 'formats'], + list: ['views', 'data'], + form: ['data', 'views', 'list', 'formats'], + list_editable: ['list', 'form', 'data'], + }; + + return { + /** + * Function which does not do anything + */ + noop: function () { }, + /** + * Loads 'base.xml' template file into qweb for the provided instance + * + * @param instance openerp instance being initialized, to load the template file in + */ + loadTemplate: function (instance) { + instance.web.qweb.add_template(doc); + }, + /** + * Alter provided instance's ``session`` attribute to make response + * mockable: + * + * * The ``responses`` parameter can be used to provide a map of (RPC) + * paths (e.g. ``/web/view/load``) to a function returning a response + * to the query. + * * ``instance.session`` grows a ``responses`` attribute which is + * a map of the same (and is in fact initialized to the ``responses`` + * parameter if one is provided) + * + * Note that RPC requests to un-mocked URLs will be rejected with an + * error message: only explicitly specified urls will get a response. + * + * Mocked sessions will *never* perform an actual RPC connection. + * + * @param instance openerp instance being initialized + * @param {Object} [responses] + */ + mockifyRPC: function (instance, responses) { + var session = instance.session; + session.responses = responses || {}; + session.rpc_function = function (url, payload) { + var fn = this.responses[url.url + ':' + payload.params.method] + || this.responses[url.url]; + + if (!fn) { + return $.Deferred().reject({}, 'failed', + _.str.sprintf("Url %s not found in mock responses, with arguments %s", + url.url, JSON.stringify(payload.params)) + ).promise(); + } + return $.when(fn(payload)); + }; + }, + /** + * Creates an openerp web instance loading the specified module after + * all of its dependencies. + * + * @param {String} module + * @returns OpenERP Web instance + */ + instanceFor: function (module) { + var instance = openerp.init([]); + this._load(instance, module); + return instance; + }, + _load: function (instance, module, loaded) { + if (!loaded) { loaded = []; } + + var deps = dependencies[module]; + if (!deps) { throw new Error("Unknown dependencies for " + module); } + + var to_load = _.difference(deps, loaded); + while (!_.isEmpty(to_load)) { + this._load(instance, to_load[0], loaded); + to_load = _.difference(deps, loaded); + } + openerp.web[module](instance); + loaded.push(module); + } + } +})(); |
