[IMP] im_livechat: ported the javascript to the new openerp framework

bzr revid: nicolas.vanhoren@openerp.com-20130819123836-8ngjlvab8202tk65
This commit is contained in:
niv-openerp
2013-08-19 14:38:36 +02:00
22 changed files with 11183 additions and 11927 deletions
+1 -1
View File
@@ -1,2 +1,2 @@
<script type="text/javascript" src="{{url}}/im_livechat/static/ext/static/js/require.js"></script>
<script type="text/javascript" src="{{url}}/im_livechat/static/ext/static/lib/requirejs/require.js"></script>
<script type="text/javascript" src='{{url}}/im_livechat/loader?p={{parameters | json | escape}}'></script>
+23 -1
View File
@@ -1,19 +1,39 @@
(function() {
var tmpQWeb2 = window.QWeb2;
require.config({
context: "oelivesupport",
baseUrl: {{url | json}} + "/im_livechat/static/ext/static/js",
baseUrl: {{url | json}},
paths: {
jquery: "im_livechat/static/ext/static/lib/jquery/jquery",
underscore: "im_livechat/static/ext/static/lib/underscore/underscore",
qweb2: "im_livechat/static/ext/static/lib/qweb/qweb2",
openerp: "web/static/src/js/openerpframework",
"jquery.achtung": "im_livechat/static/ext/static/lib/jquery-achtung/src/ui.achtung",
livesupport: "im_livechat/static/ext/static/js/livesupport",
},
shim: {
underscore: {
init: function() {
return _.noConflict();
},
},
qweb2: {
init: function() {
var QWeb2 = window.QWeb2;
window.QWeb2 = tmpQWeb2;
return QWeb2;
},
},
"jquery.achtung": {
deps: ['jquery'],
},
},
})(["livesupport", "jquery"], function(livesupport, jQuery) {
jQuery.noConflict();
console.log("loaded live support");
livesupport.main({{url | json}}, {{db | json}}, "anonymous", "anonymous", {{channel | json}}, {
buttonText: {{buttonText | json}},
inputPlaceholder: {{inputPlaceholder | json}},
@@ -22,3 +42,5 @@ require.config({
userName: {{userName | json}} || undefined,
});
});
})();
+3
View File
@@ -0,0 +1,3 @@
{
"directory": "static/lib/"
}
@@ -0,0 +1,20 @@
module.exports = function(grunt) {
grunt.initConfig({
jshint: {
src: ['static/js/*.js'],
options: {
sub: true, //[] instead of .
evil: true, //eval
laxbreak: true, //unsafe line breaks
},
},
});
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.registerTask('test', []);
grunt.registerTask('default', ['jshint']);
};
+18
View File
@@ -0,0 +1,18 @@
{
"name": "im_livechat",
"version": "0.0.0",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"jquery": "1.8.3",
"underscore": "1.3.1",
"qweb": "git@github.com:OpenERP/qweb.git#~1.0.0",
"jquery-achtung": "git://github.com/joshvarner/jquery-achtung.git",
"requirejs": "~2.1.8"
}
}
@@ -0,0 +1,6 @@
{
"devDependencies": {
"grunt": "*",
"grunt-contrib-jshint": "*"
}
}
@@ -1,273 +0,0 @@
/**
* achtung 0.3.0
*
* Growl-like notifications for jQuery
*
* Copyright (c) 2009 Josh Varner <josh@voxwerk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @license http://www.opensource.org/licenses/mit-license.php
* @author Josh Varner <josh@voxwerk.com>
*/
/*globals jQuery,clearTimeout,document,navigator,setTimeout
*/
(function($) {
/**
* This is based on the jQuery UI $.widget code. I would have just made this
* a $.widget but I didn't want the jQuery UI dependency.
*/
$.fn.achtung = function(options)
{
var isMethodCall = (typeof options === 'string'),
args = Array.prototype.slice.call(arguments, 0),
name = 'achtung';
// handle initialization and non-getter methods
return this.each(function() {
var instance = $.data(this, name);
// prevent calls to internal methods
if (isMethodCall && options.substring(0, 1) === '_') {
return this;
}
// constructor
(!instance && !isMethodCall &&
$.data(this, name, new $.achtung(this))._init(args));
// method call
(instance && isMethodCall && $.isFunction(instance[options]) &&
instance[options].apply(instance, args.slice(1)));
});
};
$.achtung = function(element)
{
var args = Array.prototype.slice.call(arguments, 0), $el;
if (!element || !element.nodeType) {
$el = $('<div />');
return $el.achtung.apply($el, args);
}
this.$container = $(element);
};
/**
* Static members
**/
$.extend($.achtung, {
version: '0.3.0',
$overlay: false,
defaults: {
timeout: 10,
disableClose: false,
icon: false,
className: '',
animateClassSwitch: false,
showEffects: {'opacity':'toggle','height':'toggle'},
hideEffects: {'opacity':'toggle','height':'toggle'},
showEffectDuration: 500,
hideEffectDuration: 700
}
});
/**
* Non-static members
**/
$.extend($.achtung.prototype, {
$container: false,
closeTimer: false,
options: {},
_init: function(args)
{
var o, self = this;
args = $.isArray(args) ? args : [];
args.unshift($.achtung.defaults);
args.unshift({});
o = this.options = $.extend.apply($, args);
if (!$.achtung.$overlay) {
$.achtung.$overlay = $('<div id="achtung-overlay"></div>').appendTo(document.body);
}
if (!o.disableClose) {
$('<span class="achtung-close-button ui-icon ui-icon-close" />')
.click(function () { self.close(); })
.hover(function () { $(this).addClass('achtung-close-button-hover'); },
function () { $(this).removeClass('achtung-close-button-hover'); })
.prependTo(this.$container);
}
this.changeIcon(o.icon, true);
if (o.message) {
this.$container.append($('<span class="achtung-message">' + o.message + '</span>'));
}
(o.className && this.$container.addClass(o.className));
(o.css && this.$container.css(o.css));
this.$container
.addClass('achtung')
.appendTo($.achtung.$overlay);
if (o.showEffects) {
this.$container.toggle();
} else {
this.$container.show();
}
if (o.timeout > 0) {
this.timeout(o.timeout);
}
},
timeout: function(timeout)
{
var self = this;
if (this.closeTimer) {
clearTimeout(this.closeTimer);
}
this.closeTimer = setTimeout(function() { self.close(); }, timeout * 1000);
this.options.timeout = timeout;
},
/**
* Change the CSS class associated with this message, using
* a transition if available (not availble in Safari/Webkit).
* If no transition is available, the switch is immediate.
*
* #LATER Check if this has been corrected in Webkit or jQuery UI
* #TODO Make transition time configurable
* @param newClass string Name of new class to associate
*/
changeClass: function(newClass)
{
var self = this;
if (this.options.className === newClass) {
return;
}
this.$container.queue(function() {
if (!self.options.animateClassSwitch ||
/webkit/.test(navigator.userAgent.toLowerCase()) ||
!$.isFunction($.fn.switchClass)) {
self.$container.removeClass(self.options.className);
self.$container.addClass(newClass);
} else {
self.$container.switchClass(self.options.className, newClass, 500);
}
self.options.className = newClass;
self.$container.dequeue();
});
},
changeIcon: function(newIcon, force)
{
var self = this;
if ((force !== true || newIcon === false) && this.options.icon === newIcon) {
return;
}
if (force || this.options.icon === false) {
this.$container.prepend($('<span class="achtung-message-icon ui-icon ' + newIcon + '" />'));
this.options.icon = newIcon;
return;
} else if (newIcon === false) {
this.$container.find('.achtung-message-icon').remove();
this.options.icon = false;
return;
}
this.$container.queue(function() {
var $span = $('.achtung-message-icon', self.$container);
if (!self.options.animateClassSwitch ||
/webkit/.test(navigator.userAgent.toLowerCase()) ||
!$.isFunction($.fn.switchClass)) {
$span.removeClass(self.options.icon);
$span.addClass(newIcon);
} else {
$span.switchClass(self.options.icon, newIcon, 500);
}
self.options.icon = newIcon;
self.$container.dequeue();
});
},
changeMessage: function(newMessage)
{
this.$container.queue(function() {
$('.achtung-message', $(this)).html(newMessage);
$(this).dequeue();
});
},
update: function(options)
{
(options.className && this.changeClass(options.className));
(options.css && this.$container.css(options.css));
(typeof(options.icon) !== 'undefined' && this.changeIcon(options.icon));
(options.message && this.changeMessage(options.message));
(options.timeout && this.timeout(options.timeout));
},
close: function()
{
var o = this.options, $container = this.$container;
if (o.hideEffects) {
this.$container.animate(o.hideEffects, o.hideEffectDuration);
} else {
this.$container.hide();
}
$container.queue(function() {
$container.removeData('achtung');
$container.remove();
if ($.achtung.$overlay && $.achtung.$overlay.is(':empty')) {
$.achtung.$overlay.remove();
$.achtung.$overlay = false;
}
$container.dequeue();
});
}
});
})(jQuery);
File diff suppressed because it is too large Load Diff
@@ -1,10 +1,21 @@
define(["nova", "underscore", "oeclient", "require", "jquery",
"jquery.achtung"], function(nova, _, oeclient, require, $) {
/*
This file must compile in EcmaScript 3 and work in IE7.
*/
define(["openerp", "underscore", "require", "jquery",
"jquery.achtung"], function(openerp, _, require, $) {
/* jshint es3: true */
"use strict";
var _t = openerp._t;
var livesupport = {};
var templateEngine = new nova.TemplateEngine();
templateEngine.extendEnvironment({"toUrl": _.bind(require.toUrl, require)});
_.extend(openerp.qweb.default_dict, {
'toUrl': _.bind(require.toUrl, require)
});
var connection;
var defaultInputPlaceholder;
@@ -14,30 +25,28 @@ define(["nova", "underscore", "oeclient", "require", "jquery",
var defs = [];
options = options || {};
_.defaults(options, {
buttonText: "Chat with one of our collaborators",
inputPlaceholder: "How may I help you?",
buttonText: _t("Chat with one of our collaborators"),
inputPlaceholder: _t("How may I help you?"),
defaultMessage: null,
auto: false,
userName: "Anonymous",
userName: _t("Anonymous")
});
defaultInputPlaceholder = options.inputPlaceholder;
userName = options.userName;
defs.push($.ajax({
url: require.toUrl("./livesupport_templates.js"),
jsonp: false,
jsonpCallback: "oe_livesupport_templates_callback",
dataType: "jsonp",
cache: true,
}).then(function(content) {
return templateEngine.loadFileContent(content);
}));
defs.push(add_css("../css/livesupport.css"));
defs.push(add_css("./jquery.achtung.css"));
// TODO : load QwebTemplates
defs.push(add_css("im_livechat/static/ext/static/css/livesupport.css"));
defs.push(add_css("im_livechat/static/ext/static/lib/jquery-achtung/src/ui.achtung.css"));
$.when.apply($, defs).then(function() {
return $.when.apply($, defs).then(function() {
console.log("starting live support customer app");
connection = new oeclient.Connection(new oeclient.JsonpRPCConnector(server_url), db, login, password);
connection.connector.call("/im_livechat/available", {db: db, channel: channel}).then(function(activated) {
connection = new openerp.Session(null, server_url, { override_session: true });
return connection.session_authenticate(db, login, password);
}).then(function() {
return connection.rpc('/web/proxy/load', {path: '/im_livechat/static/ext/static/js/livesupport_templates.xml'}).then(function(xml) {
openerp.qweb.add_template(xml);
});
}).then(function() {
return connection.rpc("/im_livechat/available", {db: db, channel: channel}).then(function(activated) {
if (! activated & ! options.auto)
return;
var button = new livesupport.ChatButton(null, channel, options);
@@ -63,19 +72,19 @@ define(["nova", "underscore", "oeclient", "require", "jquery",
var ERROR_DELAY = 5000;
livesupport.ChatButton = nova.Widget.$extend({
livesupport.ChatButton = openerp.Widget.extend({
className: "openerp_style oe_chat_button",
events: {
"click": "click",
"click": "click"
},
__init__: function(parent, channel, options) {
this.$super(parent);
init: function(parent, channel, options) {
this._super(parent);
this.channel = channel;
this.options = options;
this.text = options.buttonText;
},
render: function() {
this.$().append(templateEngine.chatButton({widget: this}));
start: function() {
this.$().append(openerp.qweb.render("chatButton", {widget: this}));
},
click: function() {
if (! this.manager) {
@@ -92,34 +101,33 @@ define(["nova", "underscore", "oeclient", "require", "jquery",
def.reject();
}, 5000);
def.then(_.bind(this.chat, this), function() {
notification("It seems the connection to the server is encountering problems, please try again later.");
notification(_t("It seems the connection to the server is encountering problems, please try again later."));
});
},
chat: function() {
var self = this;
if (this.manager.conversations.length > 0)
return;
connection.getModel("im_livechat.channel").call("get_available_user", [this.channel]).then(function(user_id) {
connection.model("im_livechat.channel").call("get_available_user", [this.channel]).then(function(user_id) {
if (! user_id) {
notification("None of our collaborators seems to be available, please try again later.");
notification(_t("None of our collaborators seems to be available, please try again later."));
return;
}
self.manager.ensure_users([user_id]).then(function() {
var conv = self.manager.activate_user(self.manager.get_user(user_id), true);
if (self.options.defaultMessage) {
conv.received_message({message: self.options.defaultMessage,
date: oeclient.datetime_to_str(new Date())});
date: openerp.datetime_to_str(new Date())});
}
});
});
},
}
});
livesupport.ImUser = nova.Class.$extend({
__include__: [nova.DynamicProperties],
__init__: function(parent, user_rec) {
nova.DynamicProperties.__init__.call(this, parent);
user_rec.image_url = require.toUrl("../img/avatar/avatar.jpeg");
livesupport.ImUser = openerp.Class.extend(openerp.PropertiesMixin, {
init: function(parent, user_rec) {
openerp.PropertiesMixin.init.call(this, parent);
user_rec.image_url = require.toUrl("im_livechat/static/ext/static/img/avatar/avatar.jpeg");
if (user_rec.image)
user_rec.image_url = "data:image/png;base64," + user_rec.image;
this.set(user_rec);
@@ -131,20 +139,19 @@ define(["nova", "underscore", "oeclient", "require", "jquery",
},
destroy: function() {
this.trigger("destroyed");
nova.DynamicProperties.destroy.call(this);
openerp.PropertiesMixin.destroy.call(this);
},
add_watcher: function() {
this.set("watcher_count", this.get("watcher_count") + 1);
},
remove_watcher: function() {
this.set("watcher_count", this.get("watcher_count") - 1);
},
}
});
livesupport.ConversationManager = nova.Class.$extend({
__include__: [nova.DynamicProperties],
__init__: function(parent) {
nova.DynamicProperties.__init__.call(this, parent);
livesupport.ConversationManager = openerp.Class.extend(openerp.PropertiesMixin, {
init: function(parent) {
openerp.PropertiesMixin.init.call(this, parent);
this.set("right_offset", 0);
this.conversations = [];
this.users = {};
@@ -176,22 +183,22 @@ define(["nova", "underscore", "oeclient", "require", "jquery",
var def = $.when(uuid);
if (! uuid) {
def = connection.connector.call("/longpolling/im/gen_uuid", {});
def = connection.rpc("/longpolling/im/gen_uuid", {});
}
return def.then(function(uuid) {
localStorage["oe_livesupport_uuid"] = uuid;
return connection.getModel("im.user").call("get_by_user_id", [uuid]);
return connection.model("im.user").call("get_by_user_id", [uuid]);
}).then(function(my_id) {
self.my_id = my_id["id"];
return connection.getModel("im.user").call("assign_name", [uuid, userName]);
return connection.model("im.user").call("assign_name", [uuid, userName]);
}).then(function() {
return self.ensure_users([self.my_id])
return self.ensure_users([self.my_id]);
}).then(function() {
var me = self.users_cache[self.my_id];
delete self.users_cache[self.my_id];
self.me = me;
me.set("name", "You");
return connection.connector.call("/longpolling/im/activated", {});
return connection.rpc("/longpolling/im/activated", {});
}).then(function(activated) {
if (activated) {
self.activated = true;
@@ -203,7 +210,7 @@ define(["nova", "underscore", "oeclient", "require", "jquery",
});
},
unload: function() {
connection.getModel("im.user").call("im_disconnect", [], {uuid: this.me.get("uuid"), context: {}});
connection.model("im.user").call("im_disconnect", [], {uuid: this.me.get("uuid"), context: {}});
},
ensure_users: function(user_ids) {
var no_cache = {};
@@ -215,7 +222,7 @@ define(["nova", "underscore", "oeclient", "require", "jquery",
if (_.size(no_cache) === 0)
return $.when();
else
return connection.getModel("im.user").call("read", [_.values(no_cache), []]).then(function(users) {
return connection.model("im.user").call("read", [_.values(no_cache), []]).then(function(users) {
self.add_to_user_cache(users);
});
},
@@ -239,13 +246,13 @@ define(["nova", "underscore", "oeclient", "require", "jquery",
var user_ids = _.map(this.users_cache, function(el) {
return el.get("id");
});
connection.connector.call("/longpolling/im/poll", {
connection.rpc("/longpolling/im/poll", {
last: this.last,
users_watch: user_ids,
db: connection.database,
uid: connection.userId,
password: connection.password,
uuid: self.me.get("uuid"),
uuid: self.me.get("uuid")
}).then(function(result) {
_.each(result.users_status, function(el) {
if (self.get_user(el.id))
@@ -273,8 +280,8 @@ define(["nova", "underscore", "oeclient", "require", "jquery",
return;
}
this.ting = new Audio(new Audio().canPlayType("audio/ogg; codecs=vorbis") ?
require.toUrl("../audio/Ting.ogg") :
require.toUrl("../audio/Ting.mp3")
require.toUrl("im_livechat/static/ext/static/audio/Ting.ogg") :
require.toUrl("im_livechat/static/ext/static/audio/Ting.mp3")
);
},
window_focus_change: function() {
@@ -283,10 +290,10 @@ define(["nova", "underscore", "oeclient", "require", "jquery",
}
},
messages_change: function() {
/*if (! instance.webclient.set_title_part)
return;
instance.webclient.set_title_part("im_messages", this.get("waiting_messages") === 0 ? undefined :
_.str.sprintf(_t("%d Messages"), this.get("waiting_messages")));*/
//if (! instance.webclient.set_title_part)
// return;
//instance.webclient.set_title_part("im_messages", this.get("waiting_messages") === 0 ? undefined :
// _.str.sprintf(_t("%d Messages"), this.get("waiting_messages")));
},
activate_user: function(user, focus) {
var conv = this.users[user.get('id')];
@@ -326,19 +333,19 @@ define(["nova", "underscore", "oeclient", "require", "jquery",
$(window).off("unload", this.unload_event_handler);
$(window).unbind("blur", this.blur_hdl);
$(window).unbind("focus", this.focus_hdl);
nova.DynamicProperties.destroy.call(this);
},
openerp.PropertiesMixin.destroy.call(this);
}
});
livesupport.Conversation = nova.Widget.$extend({
livesupport.Conversation = openerp.Widget.extend({
className: "openerp_style oe_im_chatview",
events: {
"keydown input": "send_message",
"click .oe_im_chatview_close": "destroy",
"click .oe_im_chatview_header": "show_hide",
"click .oe_im_chatview_header": "show_hide"
},
__init__: function(parent, user, me) {
this.$super(parent);
init: function(parent, user, me) {
this._super(parent);
this.me = me;
this.user = user;
this.user.add_watcher();
@@ -347,8 +354,8 @@ define(["nova", "underscore", "oeclient", "require", "jquery",
this.set("pending", 0);
this.inputPlaceholder = defaultInputPlaceholder;
},
render: function() {
this.$().append(templateEngine.conversation({widget: this}));
start: function() {
this.$().append(openerp.qweb.render("conversation", {widget: this}));
var change_status = function() {
this.$().toggleClass("oe_im_chatview_disconnected_status", this.user.get("im_status") === false);
this.$(".oe_im_chatview_online").toggle(this.user.get("im_status") === true);
@@ -371,11 +378,11 @@ define(["nova", "underscore", "oeclient", "require", "jquery",
show_hide: function() {
if (this.shown) {
this.$().animate({
height: this.$(".oe_im_chatview_header").outerHeight(),
height: this.$(".oe_im_chatview_header").outerHeight()
});
} else {
this.$().animate({
height: this.full_height,
height: this.full_height
});
}
this.shown = ! this.shown;
@@ -392,7 +399,7 @@ define(["nova", "underscore", "oeclient", "require", "jquery",
} else {
this.set("pending", this.get("pending") + 1);
}
this._add_bubble(this.user, message.message, oeclient.str_to_datetime(message.date));
this._add_bubble(this.user, message.message, openerp.str_to_datetime(message.date));
},
send_message: function(e) {
if(e && e.which !== 13) {
@@ -404,7 +411,7 @@ define(["nova", "underscore", "oeclient", "require", "jquery",
}
this.$("input").val("");
var send_it = _.bind(function() {
var model = connection.getModel("im.message");
var model = connection.model("im.message");
return model.call("post", [mes, this.user.get('id')], {uuid: this.me.get("uuid"), context: {}});
}, this);
var tries = 0;
@@ -430,7 +437,7 @@ define(["nova", "underscore", "oeclient", "require", "jquery",
};
date = "" + zpad(date.getHours(), 2) + ":" + zpad(date.getMinutes(), 2);
this.last_bubble = $(templateEngine.conversation_bubble({"items": items, "user": user, "time": date}));
this.last_bubble = $(openerp.qweb.render("conversation_bubble", {"items": items, "user": user, "time": date}));
$(this.$(".oe_im_chatview_content").children()[0]).append(this.last_bubble);
this._go_bottom();
},
@@ -443,8 +450,8 @@ define(["nova", "underscore", "oeclient", "require", "jquery",
destroy: function() {
this.user.remove_watcher();
this.trigger("destroyed");
return this.$super();
},
return this._super();
}
});
@@ -1,36 +0,0 @@
<%def name="conversation">
<div class="oe_im_chatview_header">
<img src="${toUrl('../img/green.png')}" class="oe_im_chatview_online"/>
${widget.user.get('name') || 'You'}
<button class="oe_im_chatview_close">×</button>
</div>
<div class="oe_im_chatview_disconnected">
${widget.user.get("name") + " is offline. He/She will receive your messages on his/her next connection."}
</div>
<div class="oe_im_chatview_content">
<div></div>
</div>
<div class="oe_im_chatview_footer">
<input class="oe_im_chatview_input" placeholder="${widget.inputPlaceholder}" />
</div>
</%def>
<%def name="conversation_bubble">
<div class="oe_im_chatview_bubble">
<div class="oe_im_chatview_clip">
<img class="oe_im_chatview_avatar" src="${user.get('image_url')}"/>
</div>
<div class="oe_im_chatview_from">${user.get('name') || 'You'}</div>
<div class="oe_im_chatview_bubble_list">
% _.each(items, function(item) {
<div class="oe_im_chatview_bubble_item">${item}</div>
% });
</div>
<div class="oe_im_chatview_time">${time}</div>
</div>
</%def>
<%def name="chatButton">
${widget.text}
</%def>
@@ -1 +0,0 @@
window.oe_livesupport_templates_callback("\n<%def name=\"conversation\">\n <div class=\"oe_im_chatview_header\">\n <img src=\"${toUrl('../img/green.png')}\" class=\"oe_im_chatview_online\"/>\n ${widget.user.get('name') || 'You'}\n <button class=\"oe_im_chatview_close\">\u00d7</button>\n </div>\n <div class=\"oe_im_chatview_disconnected\">\n ${widget.user.get(\"name\") + \" is offline. He/She will receive your messages on his/her next connection.\"}\n </div>\n <div class=\"oe_im_chatview_content\">\n <div></div>\n </div>\n <div class=\"oe_im_chatview_footer\">\n <input class=\"oe_im_chatview_input\" placeholder=\"${widget.inputPlaceholder}\" />\n </div>\n</%def>\n\n<%def name=\"conversation_bubble\">\n <div class=\"oe_im_chatview_bubble\">\n <div class=\"oe_im_chatview_clip\">\n <img class=\"oe_im_chatview_avatar\" src=\"${user.get('image_url')}\"/>\n </div>\n <div class=\"oe_im_chatview_from\">${user.get('name') || 'You'}</div>\n <div class=\"oe_im_chatview_bubble_list\">\n % _.each(items, function(item) {\n <div class=\"oe_im_chatview_bubble_item\">${item}</div>\n % });\n </div>\n <div class=\"oe_im_chatview_time\">${time}</div>\n </div>\n</%def>\n\n<%def name=\"chatButton\">\n ${widget.text}\n</%def>");
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-name="conversation">
<div class="oe_im_chatview_header">
<img t-att-src="toUrl('im_livechat/static/ext/static/img/green.png')" class="oe_im_chatview_online"/>
<t t-esc="widget.user.get('name') || _t('You')"/>
<button class="oe_im_chatview_close">×</button>
</div>
<div class="oe_im_chatview_disconnected">
<t t-esc='widget.user.get("name") + _t(" is offline. He/She will receive your messages on his/her next connection.")'/>
</div>
<div class="oe_im_chatview_content">
<div></div>
</div>
<div class="oe_im_chatview_footer">
<input class="oe_im_chatview_input" t-att-placeholder="widget.inputPlaceholder" />
</div>
</t>
<t t-name="conversation_bubble">
<div class="oe_im_chatview_bubble">
<div class="oe_im_chatview_clip">
<img class="oe_im_chatview_avatar" t-att-src="user.get('image_url')"/>
</div>
<div class="oe_im_chatview_from"><t t-esc="user.get('name') || _t('You')"/></div>
<div class="oe_im_chatview_bubble_list">
<t t-foreach="items" t-as="item">
<div class="oe_im_chatview_bubble_item"><t t-esc="item"/></div>
</t>
</div>
<div class="oe_im_chatview_time"><t t-esc="time"/></div>
</div>
</t>
<t t-name="chatButton">
<t t-esc="widget.text"/>
</t>
</templates>
@@ -1,988 +0,0 @@
/*
Copyright (c) 2012, Nicolas Vanhoren
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
(function() {
if (typeof(define) !== "undefined") { // requirejs
define(["jquery", "underscore"], nova_declare);
} else if (typeof(exports) !== "undefined") { // node
var _ = require("underscore")
_.extend(exports, nova_declare(null, _));
} else { // define global variable 'nova'
nova = nova_declare($, _);
}
function nova_declare($, _) {
var nova = {};
nova.internal = {};
/*
* Modified Armin Ronacher's Classy library.
*
* Defines The Class object. That object can be used to define and inherit classes using
* the $extend() method.
*
* Example:
*
* var Person = nova.Class.$extend({
* __init__: function(isDancing){
* this.dancing = isDancing;
* },
* dance: function(){
* return this.dancing;
* }
* });
*
* The __init__() method act as a constructor. This class can be instancied this way:
*
* var person = new Person(true);
* person.dance();
*
* The Person class can also be extended again:
*
* var Ninja = Person.$extend({
* __init__: function(){
* this.$super( false );
* },
* dance: function(){
* // Call the inherited version of dance()
* return this.$super();
* },
* swingSword: function(){
* return true;
* }
* });
*
* When extending a class, each re-defined method can use this.$super() to call the previous
* implementation of that method.
*/
/**
* Classy - classy classes for JavaScript
*
* :copyright: (c) 2011 by Armin Ronacher.
* :license: BSD.
*/
(function(){
var
context = this,
disable_constructor = false;
/* we check if $super is in use by a class if we can. But first we have to
check if the JavaScript interpreter supports that. This also matches
to false positives later, but that does not do any harm besides slightly
slowing calls down. */
var probe_super = (function(){this.$super();}).toString().indexOf('$super') > 0;
function usesSuper(obj) {
return !probe_super || /\B\$super\b/.test(obj.toString());
}
/* helper function to set the attribute of something to a value or
removes it if the value is undefined. */
function setOrUnset(obj, key, value) {
if (value === undefined)
delete obj[key];
else
obj[key] = value;
}
/* gets the own property of an object */
function getOwnProperty(obj, name) {
return Object.prototype.hasOwnProperty.call(obj, name)
? obj[name] : undefined;
}
/* instanciate a class without calling the constructor */
function cheapNew(cls) {
disable_constructor = true;
var rv = new cls;
disable_constructor = false;
return rv;
}
/* the base class we export */
var Class = function() {};
/* extend functionality */
Class.$extend = function(properties) {
var super_prototype = this.prototype;
/* disable constructors and instanciate prototype. Because the
prototype can't raise an exception when created, we are safe
without a try/finally here. */
var prototype = cheapNew(this);
/* copy all properties of the includes over if there are any */
prototype.__mixin_ids = _.clone(prototype.__mixin_ids || {});
if (properties.__include__)
for (var i = 0, n = properties.__include__.length; i != n; ++i) {
var mixin = properties.__include__[i];
if (mixin instanceof nova.Mixin) {
_.extend(prototype.__mixin_ids, mixin.__mixin_ids);
mixin = mixin.__mixin_properties;
}
for (var name in mixin) {
var value = getOwnProperty(mixin, name);
if (value !== undefined)
prototype[name] = mixin[name];
}
}
/* copy class vars from the superclass */
properties.__classvars__ = properties.__classvars__ || {};
if (prototype.__classvars__)
for (var key in prototype.__classvars__)
if (!properties.__classvars__[key]) {
var value = getOwnProperty(prototype.__classvars__, key);
properties.__classvars__[key] = value;
}
/* copy all properties over to the new prototype */
for (var name in properties) {
var value = getOwnProperty(properties, name);
if (name === '__include__' ||
value === undefined)
continue;
prototype[name] = typeof value === 'function' && usesSuper(value) ?
(function(meth, name) {
return function() {
var old_super = getOwnProperty(this, '$super');
this.$super = super_prototype[name];
try {
return meth.apply(this, arguments);
}
finally {
setOrUnset(this, '$super', old_super);
}
};
})(value, name) : value
}
var class_init = this.__class_init__ || function() {};
var p_class_init = prototype.__class_init__ || function() {};
delete prototype.__class_init__;
var n_class_init = function() {
class_init.apply(null, arguments);
p_class_init.apply(null, arguments);
}
n_class_init(prototype);
/* dummy constructor */
var instance = function() {
if (disable_constructor)
return;
var proper_this = context === this ? cheapNew(arguments.callee) : this;
if (proper_this.__init__)
proper_this.__init__.apply(proper_this, arguments);
proper_this.$class = instance;
return proper_this;
}
/* copy all class vars over of any */
for (var key in properties.__classvars__) {
var value = getOwnProperty(properties.__classvars__, key);
if (value !== undefined)
instance[key] = value;
}
/* copy prototype and constructor over, reattach $extend and
return the class */
instance.prototype = prototype;
instance.constructor = instance;
instance.$extend = this.$extend;
instance.$withData = this.$withData;
instance.__class_init__ = n_class_init;
return instance;
};
/* instanciate with data functionality */
Class.$withData = function(data) {
var rv = cheapNew(this);
for (var key in data) {
var value = getOwnProperty(data, key);
if (value !== undefined)
rv[key] = value;
}
return rv;
};
/* export the class */
this.Class = Class;
}).call(nova);
// end of Armin Ronacher's code
var mixinId = 1;
nova.Mixin = nova.Class.$extend({
__init__: function() {
this.__mixin_properties = {};
this.__mixin_id = mixinId;
mixinId++;
this.__mixin_ids = {};
this.__mixin_ids[this.__mixin_id] = true;
_.each(_.toArray(arguments), function(el) {
if (el instanceof nova.Mixin) {
_.extend(this.__mixin_properties, el.__mixin_properties);
_.extend(this.__mixin_ids, el.__mixin_ids);
} else { // object
_.extend(this.__mixin_properties, el)
}
}, this);
_.extend(this, this.__mixin_properties);
}
});
nova.Interface = nova.Mixin.$extend({
__init__: function() {
var lst = [];
_.each(_.toArray(arguments), function(el) {
if (el instanceof nova.Interface) {
lst.push(el);
} else if (el instanceof nova.Mixin) {
var tmp = new nova.Interface(el.__mixin_properties);
tmp.__mixin_ids = el.__mixin_ids;
lst.push(tmp);
} else { // object
var nprops = {};
_.each(el, function(v, k) {
nprops[k] = function() {
throw new nova.NotImplementedError();
};
});
lst.push(nprops);
}
});
this.$super.apply(this, lst);
}
});
nova.hasMixin = function(object, mixin) {
if (! object)
return false;
return (object.__mixin_ids || {})[mixin.__mixin_id] === true;
};
var ErrorBase = function() {
};
ErrorBase.prototype = new Error();
ErrorBase.$extend = nova.Class.$extend;
ErrorBase.$withData = nova.Class.$withData;
nova.Error = ErrorBase.$extend({
name: "nova.Error",
defaultMessage: "",
__init__: function(message) {
this.message = message || this.defaultMessage;
}
});
nova.NotImplementedError = nova.Error.$extend({
name: "nova.NotImplementedError",
defaultMessage: "This method is not implemented"
});
nova.InvalidArgumentError = nova.Error.$extend({
name: "nova.InvalidArgumentError"
});
/**
* Mixin to express the concept of destroying an object.
* When an object is destroyed, it should release any resource
* it could have reserved before.
*/
nova.Destroyable = new nova.Mixin({
__init__: function() {
this.__destroyableDestroyed = false;
},
/**
* Returns true if destroy() was called on the current object.
*/
isDestroyed : function() {
return this.__destroyableDestroyed;
},
/**
* Inform the object it should destroy itself, releasing any
* resource it could have reserved.
*/
destroy : function() {
this.__destroyableDestroyed = true;
}
});
/**
* Mixin to structure objects' life-cycles folowing a parent-children
* relationship. Each object can a have a parent and multiple children.
* When an object is destroyed, all its children are destroyed too.
*/
nova.Parented = new nova.Mixin(nova.Destroyable, {
__parentedMixin : true,
__init__: function() {
nova.Destroyable.__init__.apply(this);
this.__parentedChildren = [];
this.__parentedParent = null;
},
/**
* Set the parent of the current object. When calling this method, the
* parent will also be informed and will return the current object
* when its getChildren() method is called. If the current object did
* already have a parent, it is unregistered before, which means the
* previous parent will not return the current object anymore when its
* getChildren() method is called.
*/
setParent : function(parent) {
if (this.getParent()) {
if (this.getParent().__parentedMixin) {
this.getParent().__parentedChildren = _.without(this
.getParent().getChildren(), this);
}
}
this.__parentedParent = parent;
if (parent && parent.__parentedMixin) {
parent.__parentedChildren.push(this);
}
},
/**
* Return the current parent of the object (or null).
*/
getParent : function() {
return this.__parentedParent;
},
/**
* Return a list of the children of the current object.
*/
getChildren : function() {
return _.clone(this.__parentedChildren);
},
destroy : function() {
_.each(this.getChildren(), function(el) {
el.destroy();
});
this.setParent(undefined);
nova.Destroyable.destroy.apply(this);
}
});
/*
* Yes, we steal Backbone's events :)
*
* This class just handle the dispatching of events, it is not meant to be extended,
* nor used directly. All integration with parenting and automatic unregistration of
* events is done in the mixin EventDispatcher.
*/
// (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Backbone may be freely distributed under the MIT license.
// For all details and documentation:
// http://backbonejs.org
nova.internal.Events = nova.Class.$extend({
on : function(events, callback, context) {
var ev;
events = events.split(/\s+/);
var calls = this._callbacks || (this._callbacks = {});
while (ev = events.shift()) {
var list = calls[ev] || (calls[ev] = {});
var tail = list.tail || (list.tail = list.next = {});
tail.callback = callback;
tail.context = context;
list.tail = tail.next = {};
}
return this;
},
off : function(events, callback, context) {
var ev, calls, node;
if (!events) {
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;
while ((node = node.next) && node.next) {
if (node.callback === callback
&& (!context || node.context === context))
continue;
this.on(ev, node.callback, node.context);
}
}
}
return this;
},
callbackList: function() {
var lst = [];
_.each(this._callbacks || {}, function(el, eventName) {
var node = el;
while ((node = node.next) && node.next) {
lst.push([eventName, node.callback, node.context]);
}
});
return lst;
},
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
});
}
rest = Array.prototype.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);
}
}
return this;
}
});
// end of Backbone's events class
nova.EventDispatcher = new nova.Mixin(nova.Parented, {
__eventDispatcherMixin: true,
__init__: function() {
nova.Parented.__init__.apply(this);
this.__edispatcherEvents = new nova.internal.Events();
this.__edispatcherRegisteredEvents = [];
},
on: function(events, dest, func) {
var self = this;
events = events.split(/\s+/);
_.each(events, function(eventName) {
self.__edispatcherEvents.on(eventName, func, dest);
if (dest && dest.__eventDispatcherMixin) {
dest.__edispatcherRegisteredEvents.push({name: eventName, func: func, source: self});
}
});
return this;
},
off: function(events, dest, func) {
var self = this;
events = events.split(/\s+/);
_.each(events, function(eventName) {
self.__edispatcherEvents.off(eventName, func, dest);
if (dest && dest.__eventDispatcherMixin) {
dest.__edispatcherRegisteredEvents = _.filter(dest.__edispatcherRegisteredEvents, function(el) {
return !(el.name === eventName && el.func === func && el.source === self);
});
}
});
return this;
},
trigger: function(events) {
this.__edispatcherEvents.trigger.apply(this.__edispatcherEvents, arguments);
return this;
},
destroy: function() {
var self = this;
_.each(this.__edispatcherRegisteredEvents, function(event) {
event.source.__edispatcherEvents.off(event.name, event.func, self);
});
this.__edispatcherRegisteredEvents = [];
_.each(this.__edispatcherEvents.callbackList(), function(cal) {
this.off(cal[0], cal[2], cal[1]);
}, this);
this.__edispatcherEvents.off();
nova.Parented.destroy.apply(this);
}
});
nova.Properties = new nova.Mixin(nova.EventDispatcher, {
__class_init__: function(proto) {
var props = {};
_.each(proto.__properties || {}, function(v, k) {
props[k] = _.clone(v);
});
_.each(proto, function(v, k) {
if (typeof v === "function") {
var res = /^((?:get)|(?:set))([A-Z]\w*)$/.exec(k);
if (! res)
return;
var name = res[2][0].toLowerCase() + res[2].slice(1);
var prop = props[name] || (props[name] = {});
prop[res[1]] = v;
}
});
proto.__properties = props;
},
__init__: function() {
nova.EventDispatcher.__init__.apply(this);
this.__dynamicProperties = {};
},
set: function(arg1, arg2) {
var self = this;
var map;
if (typeof arg1 === "string") {
map = {};
map[arg1] = arg2;
} else {
map = arg1;
}
var tmp_set = this.__props_setting;
this.__props_setting = false;
_.each(map, function(val, key) {
var prop = self.__properties[key];
if (prop) {
if (! prop.set)
throw new nova.InvalidArgumentError("Property " + key + " does not have a setter method.");
prop.set.call(self, val);
} else {
self.fallbackSet(key, val);
}
});
this.__props_setting = tmp_set;
if (! this.__props_setting && this.__props_setted) {
this.__props_setted = false;
self.trigger("change", self);
}
},
get: function(key) {
var prop = this.__properties[key];
if (prop) {
if (! prop.get)
throw new nova.InvalidArgumentError("Property " + key + " does not have a getter method.");
return prop.get.call(this);
} else {
return this.fallbackGet(key);
}
},
fallbackSet: function(key, val) {
throw new nova.InvalidArgumentError("Property " + key + " is not defined.");
},
fallbackGet: function(key) {
throw new nova.InvalidArgumentError("Property " + key + " is not defined.");
},
trigger: function(name) {
nova.EventDispatcher.trigger.apply(this, arguments);
if (/(\s|^)change\:.*/.exec(name)) {
if (! this.__props_setting)
this.trigger("change");
else
this.__props_setted = true;
}
}
});
nova.DynamicProperties = new nova.Mixin(nova.Properties, {
__init__: function() {
nova.Properties.__init__.apply(this);
this.__dynamicProperties = {};
},
fallbackSet: function(key, val) {
var tmp = this.__dynamicProperties[key];
if (tmp === val)
return;
this.__dynamicProperties[key] = val;
this.trigger("change:" + key, this, {
oldValue: tmp,
newValue: val
});
},
fallbackGet: function(key) {
return this.__dynamicProperties[key];
}
});
nova.Widget = nova.Class.$extend({
__include__ : [nova.DynamicProperties],
tagName: 'div',
className: '',
attributes: {},
events: {},
__init__: function(parent) {
nova.Properties.__init__.apply(this);
this.__widget_element = $(document.createElement(this.tagName));
this.$().addClass(this.className);
_.each(this.attributes, function(val, key) {
this.$().attr(key, val);
}, this);
_.each(this.events, function(val, key) {
key = key.split(" ");
val = _.bind(typeof val === "string" ? this[val] : val, this);
if (key.length > 1) {
this.$().on(key[0], key[1], val);
} else {
this.$().on(key[0], val);
}
}, this);
this.setParent(parent);
},
$: function(attr) {
if (attr)
return this.__widget_element.find.apply(this.__widget_element, arguments);
else
return this.__widget_element;
},
/**
* Destroys the current widget, also destroys all its children before destroying itself.
*/
destroy: function() {
_.each(this.getChildren(), function(el) {
el.destroy();
});
this.$().remove();
nova.Properties.destroy.apply(this);
},
/**
* Renders the current widget and appends it to the given jQuery object or Widget.
*
* @param target A jQuery object or a Widget instance.
*/
appendTo: function(target) {
this.$().appendTo(target);
return this.render();
},
/**
* Renders the current widget and prepends it to the given jQuery object or Widget.
*
* @param target A jQuery object or a Widget instance.
*/
prependTo: function(target) {
this.$().prependTo(target);
return this.render();
},
/**
* Renders the current widget and inserts it after to the given jQuery object or Widget.
*
* @param target A jQuery object or a Widget instance.
*/
insertAfter: function(target) {
this.$().insertAfter(target);
return this.render();
},
/**
* Renders the current widget and inserts it before to the given jQuery object or Widget.
*
* @param target A jQuery object or a Widget instance.
*/
insertBefore: function(target) {
this.$().insertBefore(target);
return this.render();
},
/**
* Renders the current widget and replaces the given jQuery object.
*
* @param target A jQuery object or a Widget instance.
*/
replace: function(target) {
this.$().replace(target);
return this.render();
},
/**
* This is the method to implement to render the Widget.
*/
render: function() {}
});
/*
Nova Template Engine
*/
var escape_ = function(text) {
return JSON.stringify(text);
}
var indent_ = function(txt) {
var tmp = _.map(txt.split("\n"), function(x) { return " " + x; });
tmp.pop();
tmp.push("");
return tmp.join("\n");
};
var tparams = {
def_begin: /<%\s*def\s+(?:name=(?:(?:"(.+?)")|(?:'(.+?)')))\s*>/g,
def_end: /<\/%\s*def\s*>/g,
comment_multi_begin: /<%\s*doc\s*>/g,
comment_multi_end: /<\/%\s*doc\s*>/g,
eval_long_begin: /<%/g,
eval_long_end: /%>/g,
eval_short_begin: /(?:^|\n)[[ \t]*%(?!{)/g,
eval_short_end: /\n|$/g,
escape_begin: /\${/g,
interpolate_begin: /%{/g,
comment_begin: /##/g,
comment_end: /\n|$/g
};
// /<%\s*def\s+(?:name=(?:"(.+?)"))\s*%>([\s\S]*?)<%\s*def\s*%>/g
var allbegin = new RegExp(
"((?:\\\\)*)(" +
"(" + tparams.def_begin.source + ")|" +
"(" + tparams.def_end.source + ")|" +
"(" + tparams.comment_multi_begin.source + ")|" +
"(" + tparams.eval_long_begin.source + ")|" +
"(" + tparams.interpolate_begin.source + ")|" +
"(" + tparams.eval_short_begin.source + ")|" +
"(" + tparams.escape_begin.source + ")|" +
"(" + tparams.comment_begin.source + ")" +
")"
, "g");
allbegin.global = true;
var regexes = {
slashes: 1,
match: 2,
def_begin: 3,
def_name1: 4,
def_name2: 5,
def_end: 6,
comment_multi_begin: 7,
eval_long: 8,
interpolate: 9,
eval_short: 10,
escape: 11,
comment: 12
};
var regex_count = 4;
var compileTemplate = function(text, options) {
options = _.extend({start: 0, indent: true}, options);
start = options.start;
var source = "";
var current = start;
allbegin.lastIndex = current;
var text_end = text.length;
var restart = end;
var found;
var functions = [];
var indent = options.indent ? indent_ : function (txt) { return txt; };
var rmWhite = options.removeWhitespaces ? function(txt) {
if (! txt)
return txt;
txt = _.map(txt.split("\n"), function(x) { return x.trim() });
var last = txt.pop();
txt = _.reject(txt, function(x) { return !x });
txt.push(last);
return txt.join("\n") || "\n";
} : function(x) { return x };
while (found = allbegin.exec(text)) {
var to_add = rmWhite(text.slice(current, found.index));
source += to_add ? "__p+=" + escape_(to_add) + ";\n" : '';
current = found.index;
// slash escaping handling
var slashes = found[regexes.slashes] || "";
var nbr = slashes.length;
var nslash = slashes.slice(0, Math.floor(nbr / 2));
source += nbr !== 0 ? "__p+=" + escape_(nslash) + ";\n" : "";
if (nbr % 2 !== 0) {
source += "__p+=" + escape_(found[regexes.match]) + ";\n";
current = found.index + found[0].length;
allbegin.lastIndex = current;
continue;
}
if (found[regexes.def_begin]) {
var sub_compile = compileTemplate(text, _.extend({}, options, {start: found.index + found[0].length}));
var name = (found[regexes.def_name1] || found[regexes.def_name2]);
source += "var " + name + " = function(context) {\n" + indent(sub_compile.header + sub_compile.source
+ sub_compile.footer) + "}\n";
functions.push(name);
current = sub_compile.end;
} else if (found[regexes.def_end]) {
text_end = found.index;
restart = found.index + found[0].length;
break;
} else if (found[regexes.comment_multi_begin]) {
tparams.comment_multi_end.lastIndex = found.index + found[0].length;
var end = tparams.comment_multi_end.exec(text);
if (!end)
throw new Error("<%doc> without corresponding </%doc>");
current = end.index + end[0].length;
} else if (found[regexes.eval_long]) {
tparams.eval_long_end.lastIndex = found.index + found[0].length;
var end = tparams.eval_long_end.exec(text);
if (!end)
throw new Error("<% without matching %>");
var code = text.slice(found.index + found[0].length, end.index);
code = _(code.split("\n")).chain().map(function(x) { return x.trim() })
.reject(function(x) { return !x }).value().join("\n");
source += code + "\n";
current = end.index + end[0].length;
} else if (found[regexes.interpolate]) {
var braces = /{|}/g;
braces.lastIndex = found.index + found[0].length;
var b_count = 1;
var brace;
while (brace = braces.exec(text)) {
if (brace[0] === "{")
b_count++;
else {
b_count--;
}
if (b_count === 0)
break;
}
if (b_count !== 0)
throw new Error("%{ without a matching }");
source += "__p+=" + text.slice(found.index + found[0].length, brace.index) + ";\n"
current = brace.index + brace[0].length;
} else if (found[regexes.eval_short]) {
tparams.eval_short_end.lastIndex = found.index + found[0].length;
var end = tparams.eval_short_end.exec(text);
if (!end)
throw new Error("impossible state!!");
source += text.slice(found.index + found[0].length, end.index).trim() + "\n";
current = end.index;
} else if (found[regexes.escape]) {
var braces = /{|}/g;
braces.lastIndex = found.index + found[0].length;
var b_count = 1;
var brace;
while (brace = braces.exec(text)) {
if (brace[0] === "{")
b_count++;
else {
b_count--;
}
if (b_count === 0)
break;
}
if (b_count !== 0)
throw new Error("${ without a matching }");
source += "__p+=_.escape(" + text.slice(found.index + found[0].length, brace.index) + ");\n"
current = brace.index + brace[0].length;
} else { // comment
tparams.comment_end.lastIndex = found.index + found[0].length;
var end = tparams.comment_end.exec(text);
if (!end)
throw new Error("impossible state!!");
current = end.index + end[0].length;
}
allbegin.lastIndex = current;
}
var to_add = rmWhite(text.slice(current, text_end));
source += to_add ? "__p+=" + escape_(to_add) + ";\n" : "";
var header = "var __p = ''; var print = function() { __p+=Array.prototype.join.call(arguments, '') };\n" +
"with (context || {}) {\n";
var footer = "}\nreturn __p;\n";
source = indent(source);
return {
header: header,
source: source,
footer: footer,
end: restart,
functions: functions,
};
};
nova.TemplateEngine = nova.Class.$extend({
__init__: function() {
this.resetEnvironment();
this.options = {
includeInDom: $ ? true : false,
indent: true,
removeWhitespaces: true,
};
},
loadFile: function(filename) {
var self = this;
return $.get(filename).pipe(function(content) {
return self.loadFileContent(content);
});
},
loadFileContent: function(file_content) {
var code = this.compileFile(file_content);
if (this.options.includeInDom) {
var varname = _.uniqueId("novajstemplate");
var previous = window[varname];
code = "window." + varname + " = " + code + ";";
var def = $.Deferred();
var script = document.createElement("script");
script.type = "text/javascript";
script.text = code;
$("head")[0].appendChild(script);
$(script).ready(function() {
def.resolve();
});
def.then(_.bind(function() {
var tmp = window[varname];
window[varname] = previous;
this.includeTemplates(tmp);
}, this));
return def;
} else {
console.log("return (" + code + ")(context);");
return this.includeTemplates(new Function('context', "return (" + code + ")(context);"));
}
},
compileFile: function(file_content) {
var result = compileTemplate(file_content, _.extend({}, this.options));
var to_append = "";
_.each(result.functions, function(name) {
to_append += name + ": " + name + ",\n";
}, this);
to_append = this.options.indent ? indent_(to_append) : to_append;
to_append = "return {\n" + to_append + "};\n";
to_append = this.options.indent ? indent_(to_append) : to_append;
var code = "function(context) {\n" + result.header +
result.source + to_append + result.footer + "}\n";
return code;
},
includeTemplates: function(fct) {
var add = _.extend({engine: this}, this._env);
var functions = fct(add);
_.each(functions, function(func, name) {
if (this[name])
throw new Error("The template '" + name + "' is already defined");
this[name] = func;
}, this);
},
buildTemplate: function(text) {
var comp = compileTemplate(text, _.extend({}, this.options));
var result = comp.header + comp.source + comp.footer;
var add = _.extend({engine: this}, this._env);
var func = new Function('context', result);
return function(data) {
return func.call(this, _.extend(add, data));
};
},
eval: function(text, context) {
return this.buildTemplate(text)(context);
},
resetEnvironment: function(nenv) {
this._env = {_: _};
this.extendEnvironment(nenv);
},
extendEnvironment: function(env) {
_.extend(this._env, env || {});
},
});
return nova;
};
})();
@@ -1,362 +0,0 @@
/*
Copyright (c) 2013, OpenERP
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
define(["underscore", "jquery", "nova"], function(_, $, nova) {
var oeclient = {};
var genericJsonRPC = function(fct_name, params, fct) {
var data = {
jsonrpc: "2.0",
method: fct_name,
params: params,
id: Math.floor(Math.random()* (1000*1000*1000)),
};
return fct(data).pipe(function(result) {
if (result.error !== undefined) {
console.error("Server application error", result.error);
return $.Deferred().reject("server", result.error);
} else {
return result.result;
}
}, function() {
console.error("JsonRPC communication error", _.toArray(arguments));
var def = $.Deferred();
return def.reject.apply(def, ["communication"].concat(_.toArray(arguments)));
});
};
oeclient.jsonRpc = function(url, fct_name, params, settings) {
return genericJsonRPC(fct_name, params, function(data) {
return $.ajax(url, _.extend({}, settings, {
url: url,
dataType: 'json',
type: 'POST',
data: JSON.stringify(data),
contentType: 'application/json',
}));
});
};
oeclient.jsonpRpc = function(url, fct_name, params, settings) {
return genericJsonRPC(fct_name, params, function(data) {
return $.ajax(url, _.extend({}, settings, {
url: url,
dataType: 'jsonp',
jsonp: 'jsonp',
type: 'GET',
cache: false,
data: {r: JSON.stringify(data)},
}));
});
};
oeclient.Connector = nova.Class.$extend({
getService: function(serviceName) {
return new oeclient.Service(this, serviceName);
},
});
oeclient.JsonRPCConnector = oeclient.Connector.$extend({
__init__: function(url) {
this.url = url;
},
call: function(sub_url, content) {
return oeclient.jsonRpc(this.url + sub_url, "call", content);
},
send: function(serviceName, method, args) {
return this.call("/jsonrpc", {"service": serviceName, "method": method, "args": args});
},
});
oeclient.JsonpRPCConnector = oeclient.JsonRPCConnector.$extend({
call: function(sub_url, content) {
return oeclient.jsonpRpc(this.url + sub_url, "call", content);
},
});
oeclient.Service = nova.Class.$extend({
__init__: function(connector, serviceName) {
this.connector = connector;
this.serviceName = serviceName;
},
call: function(method, args) {
return this.connector.send(this.serviceName, method, args);
},
});
oeclient.AuthenticationError = nova.Error.$extend({
name: "oeclient.AuthenticationError",
defaultMessage: "An error occured during authentication."
});
oeclient.Connection = nova.Class.$extend({
__init__: function(connector, database, login, password, userId) {
this.connector = connector;
this.setLoginInfo(database, login, password, userId);
this.userContext = null;
},
setLoginInfo: function(database, login, password, userId) {
this.database = database;
this.login = login;
this.password = password;
this.userId = userId;
},
checkLogin: function(force) {
force = force === undefined ? true: force;
if (this.userId && ! force)
return $.when();
if (! this.database || ! this.login || ! this.password)
throw new oeclient.AuthenticationError();
return this.getService("common").call("login", [this.database, this.login, this.password])
.then(_.bind(function(result) {
this.userId = result;
if (! this.userId) {
console.error("Authentication failure");
return $.Deferred().reject({message:"Authentication failure"});
}
}, this));
},
getUserContext: function() {
if (! this.userContext) {
return this.getModel("res.users").call("context_get").then(_.bind(function(result) {
this.userContext = result;
return this.userContext;
}, this));
}
return $.when(this.userContext);
},
getModel: function(modelName) {
return new oeclient.Model(this, modelName);
},
getService: function(serviceName) {
return this.connector.getService(serviceName);
},
});
oeclient.Model = nova.Class.$extend({
__init__: function(connection, modelName) {
this.connection = connection;
this.modelName = modelName;
},
call: function(method, args, kw) {
return this.connection.checkLogin().then(_.bind(function() {
return this.connection.getService("object").call("execute_kw", [
this.connection.database,
this.connection.userId,
this.connection.password,
this.modelName,
method,
args || [],
kw || {},
]);
}, this));
},
search_read: function(domain, fields, offset, limit, order, context) {
return this.call("search", [domain || [], offset || 0, limit || false, order || false, context || {}]).then(_.bind(function(record_ids) {
if (! record_ids) {
return [];
}
return this.call("read", [record_ids, fields || [], context || {}]).then(function(records) {
var index = {};
_.each(records, function(r) {
index[r.id] = r;
});
var res = [];
_.each(record_ids, function(id) {
if (index[id])
res.push(index[id]);
});
return res;
});
}, this));
},
});
/**
* Converts a string to a Date javascript object using OpenERP's
* datetime string format (exemple: '2011-12-01 15:12:35').
*
* The time zone is assumed to be UTC (standard for OpenERP 6.1)
* and will be converted to the browser's time zone.
*
* @param {String} str A string representing a datetime.
* @returns {Date}
*/
oeclient.str_to_datetime = function(str) {
if(!str) {
return str;
}
var regex = /^(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d(?:\.\d+)?)$/;
var res = regex.exec(str);
if ( !res ) {
throw new Error("'" + str + "' is not a valid datetime");
}
var tmp = new Date();
tmp.setUTCFullYear(parseFloat(res[1]));
tmp.setUTCMonth(parseFloat(res[2]) - 1);
tmp.setUTCDate(parseFloat(res[3]));
tmp.setUTCHours(parseFloat(res[4]));
tmp.setUTCMinutes(parseFloat(res[5]));
tmp.setUTCSeconds(parseFloat(res[6]));
return tmp;
};
/**
* Converts a string to a Date javascript object using OpenERP's
* date string format (exemple: '2011-12-01').
*
* As a date is not subject to time zones, we assume it should be
* represented as a Date javascript object at 00:00:00 in the
* time zone of the browser.
*
* @param {String} str A string representing a date.
* @returns {Date}
*/
oeclient.str_to_date = function(str) {
if(!str) {
return str;
}
var regex = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
var res = regex.exec(str);
if ( !res ) {
throw new Error("'" + str + "' is not a valid date");
}
var tmp = new Date();
tmp.setFullYear(parseFloat(res[1]));
tmp.setMonth(parseFloat(res[2]) - 1);
tmp.setDate(parseFloat(res[3]));
tmp.setHours(0);
tmp.setMinutes(0);
tmp.setSeconds(0);
return tmp;
};
/**
* Converts a string to a Date javascript object using OpenERP's
* time string format (exemple: '15:12:35').
*
* The OpenERP times are supposed to always be naive times. We assume it is
* represented using a javascript Date with a date 1 of January 1970 and a
* time corresponding to the meant time in the browser's time zone.
*
* @param {String} str A string representing a time.
* @returns {Date}
*/
oeclient.str_to_time = function(str) {
if(!str) {
return str;
}
var regex = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)$/;
var res = regex.exec(str);
if ( !res ) {
throw new Error("'" + str + "' is not a valid time");
}
debugger;
var tmp = new Date();
tmp.setFullYear(1970);
tmp.setMonth(0);
tmp.setDate(1);
tmp.setHours(parseFloat(res[1]));
tmp.setMinutes(parseFloat(res[2]));
tmp.setSeconds(parseFloat(res[3]));
return tmp;
};
/*
* Left-pad provided arg 1 with zeroes until reaching size provided by second
* argument.
*
* @param {Number|String} str value to pad
* @param {Number} size size to reach on the final padded value
* @returns {String} padded string
*/
var zpad = function(str, size) {
str = "" + str;
return new Array(size - str.length + 1).join('0') + str;
};
/**
* Converts a Date javascript object to a string using OpenERP's
* datetime string format (exemple: '2011-12-01 15:12:35').
*
* The time zone of the Date object is assumed to be the one of the
* browser and it will be converted to UTC (standard for OpenERP 6.1).
*
* @param {Date} obj
* @returns {String} A string representing a datetime.
*/
oeclient.datetime_to_str = function(obj) {
if (!obj) {
return false;
}
return zpad(obj.getUTCFullYear(),4) + "-" + zpad(obj.getUTCMonth() + 1,2) + "-"
+ zpad(obj.getUTCDate(),2) + " " + zpad(obj.getUTCHours(),2) + ":"
+ zpad(obj.getUTCMinutes(),2) + ":" + zpad(obj.getUTCSeconds(),2);
};
/**
* Converts a Date javascript object to a string using OpenERP's
* date string format (exemple: '2011-12-01').
*
* As a date is not subject to time zones, we assume it should be
* represented as a Date javascript object at 00:00:00 in the
* time zone of the browser.
*
* @param {Date} obj
* @returns {String} A string representing a date.
*/
oeclient.date_to_str = function(obj) {
if (!obj) {
return false;
}
return zpad(obj.getFullYear(),4) + "-" + zpad(obj.getMonth() + 1,2) + "-"
+ zpad(obj.getDate(),2);
};
/**
* Converts a Date javascript object to a string using OpenERP's
* time string format (exemple: '15:12:35').
*
* The OpenERP times are supposed to always be naive times. We assume it is
* represented using a javascript Date with a date 1 of January 1970 and a
* time corresponding to the meant time in the browser's time zone.
*
* @param {Date} obj
* @returns {String} A string representing a time.
*/
oeclient.time_to_str = function(obj) {
if (!obj) {
return false;
}
return zpad(obj.getHours(),2) + ":" + zpad(obj.getMinutes(),2) + ":"
+ zpad(obj.getSeconds(),2);
};
return oeclient;
});
@@ -1,12 +0,0 @@
import sys
import json
file_name = sys.argv[1]
function_name = sys.argv[2]
with open(file_name) as file_:
content = file_.read()
print "window.%s(%s);" % (function_name, json.dumps(content))
@@ -1,6 +1,6 @@
/**
* achtung 0.3.0
*
* achtung %%VERSION%%
*
* Growl-like notifications for jQuery
*
* Copyright (c) 2009 Josh Varner <josh@voxwerk.com>
@@ -11,10 +11,10 @@
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -29,43 +29,138 @@
* @author Josh Varner <josh@voxwerk.com>
*/
#achtung-overlay {
overflow: hidden;
position: fixed;
top: 0;
right: 0;
z-index: 50;
}
/* IE 6 doesn't support position: fixed */
* html #achtung-overlay {
position:absolute;
position: absolute;
}
/* IE6 includes padding in width */
* html .achtung {
width: 280px;
width: 260px;
}
#achtung-overlay {
#achtung-wrapper {
overflow: hidden;
position: fixed;
top: 15px;
right: 15px;
width: 280px;
z-index:50;
padding: 25px 30px 10px 10px;
}
.achtung {
display:none;
display: none;
float: right;
clear: right;
margin-bottom: 8px;
padding: 15px 15px;
background-color: #000;
color: white;
width: 250px;
font-weight: bold;
position:relative;
overflow: hidden;
-moz-box-shadow: #aaa 1px 1px 2px;
-webkit-box-shadow: #aaa 1px 1px 2px;
box-shadow: #aaa 1px 1px 2px;
-moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px;
padding: 15px;
background: #000;
background: rgba(0,0,0,.95);
color: white;
width: 230px;
font-weight: normal;
font-style: italic;
font-size: 1.05em;
position: relative;
overflow: hidden;
text-shadow: -1px -1px 0 rgba(0,0,0,.3);
box-shadow: rgba(0,0,0,.3) 0 1px 4px;
-moz-box-shadow: rgba(0,0,0,.3) 0 1px 4px;
-webkit-box-shadow: rgba(0,0,0,.3) 0 1px 4px;
border-radius: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
opacity: 1.0;
filter:Alpha(Opacity=100);
}
.achtung-default {
background: #000;
background: -moz-linear-gradient(top, rgba(150,150,150,.9), rgba(120,120,120,.9) 70%);
background: -webkit-gradient(linear, left top, left bottom,
from(rgba(150,150,150,.9)),
color-stop(70%, rgba(120,120,120,.9)),
to(rgba(120,120,120,.9))
) no-repeat;
color: white;
}
.achtung .achtung-message-icon {
float: left;
margin: 0 .8em 0 -.5em;
zoom: 1;
}
.achtung .ui-icon.achtung-close-button {
overflow: hidden;
float: right;
position: relative;
top: -8px;
right: -8px;
cursor: pointer;
background-image: url('images/ui-icons_cccccc_256x240.png');
}
.achtung .ui-icon.achtung-close-button:hover {
background-image: url('images/ui-icons_ffffff_256x240.png');
}
/* Slightly darker for these colors (readability) */
.achtungSuccess, .achtungFail, .achtungWait {
/* Note that if using show/hide animations, IE will lose
this setting */
opacity: .85;
filter:Alpha(Opacity=85);
opacity: 1.0;
filter:Alpha(Opacity=100);
}
.achtungSuccess {
background: #6a5;
background: #6a5 -moz-linear-gradient(top, #8c7, #6a5 70%);
background: #6a5 -webkit-gradient(linear, left top, left bottom,
from(#8c7),
color-stop(70%, #6a5),
to(#6a5)
) no-repeat;
}
.achtungFail {
background: #a55;
background: #a55 -moz-linear-gradient(top, #c66, #a44 70%);
background: #789 -webkit-gradient(linear, left top, left bottom,
from(#c66),
color-stop(70%, #a44),
to(#a44)
) no-repeat;
}
.achtungWait {
background: #789;
background: #789 -moz-linear-gradient(top, #89a, #678 70%);
background: #789 -webkit-gradient(linear, left top, left bottom,
from(#89a),
color-stop(70%, #678),
to(#678)
) no-repeat;
}
.achtungSuccess .ui-icon.achtung-close-button,
.achtungFail .ui-icon.achtung-close-button {
background-image: url('images/ui-icons_444444_256x240.png');
}
.achtungSuccess .ui-icon.achtung-close-button:hover,
.achtungFail .ui-icon.achtung-close-button:hover {
background-image: url('images/ui-icons_ffffff_256x240.png');
}
.achtung .wait-icon {
background-image: url('images/wait.gif');
}
.achtung .achtung-message {
display: inline;
}
/**
@@ -74,8 +169,8 @@
* Can (and should) be removed if you are already loading the jQuery UI CSS
* to reduce payload size.
*/
.ui-icon { display: block; overflow: hidden; background-repeat: no-repeat; }
.ui-icon { width: 16px; height: 16px; }
.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
.ui-icon { width: 16px; height: 16px; background-image: url('images/ui-icons_222222_256x240.png'); }
.ui-icon-carat-1-n { background-position: 0 0; }
.ui-icon-carat-1-ne { background-position: -16px 0; }
.ui-icon-carat-1-e { background-position: -32px 0; }
@@ -249,58 +344,3 @@
.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
.achtung .achtung-message-icon {
margin-top: 0px;
margin-left: -.5em;
margin-right: .5em;
float: left;
zoom: 1;
}
.achtung .ui-icon.achtung-close-button {
float: right;
margin-right: -8px;
margin-top: -12px;
cursor: pointer;
color: white;
text-align: right;
}
.achtung .ui-icon.achtung-close-button:after {
content: "x"
}
/* Slightly darker for these colors (readability) */
.achtungSuccess, .achtungFail, .achtungWait {
/* Note that if using show/hide animations, IE will lose
this setting */
opacity: .93; filter:Alpha(Opacity=93);
}
.achtungSuccess {
background-color: #4DB559;
}
.achtungFail {
background-color: #D64450;
}
.achtungWait {
background-color: #658093;
}
.achtungSuccess .ui-icon.achtung-close-button,
.achtungFail .ui-icon.achtung-close-button {
}
.achtungSuccess .ui-icon.achtung-close-button-hover,
.achtungFail .ui-icon.achtung-close-button-hover {
}
.achtung .wait-icon {
}
.achtung .achtung-message {
display: inline;
}
@@ -0,0 +1,282 @@
/**
* achtung %%VERSION%%
*
* Growl-like notifications for jQuery
*
* Copyright (c) 2009 Josh Varner <josh@voxwerk.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* @license http://www.opensource.org/licenses/mit-license.php
* @author Josh Varner <josh@voxwerk.com>
*/
/*jslint browser:true, white:false, onevar:false, nomen:false, bitwise:false, plusplus:false, immed: false */
/*globals window, jQuery */
(function ($) {
var widgetName = 'achtung';
/**
* This is based on the jQuery UI $.widget code. I would have just made this
* a $.widget but I didn't want the jQuery UI dependency.
*/
$.fn.achtung = function (options) {
var isMethodCall = (typeof options === 'string'),
args = Array.prototype.slice.call(arguments, isMethodCall ? 1 : 0);
// handle initialization and non-getter methods
return this.each(function () {
// prevent calls to internal methods
if (isMethodCall && options.substring(0, 1) === '_') {
return;
}
var instance = $.data(this, widgetName);
// constructor
if (!instance && !isMethodCall) {
$.data(this, widgetName, new $.achtung(this))._init(args);
}
if (!!instance && isMethodCall && $.isFunction(instance[options])) {
instance[options].apply(instance, args);
}
});
};
$.achtung = function (element) {
if (!element || !element.nodeType) {
var el = $('<div>');
return el.achtung.apply(el, arguments);
}
this.container = $(element);
};
/**
* Static members
**/
$.extend($.achtung, {
version: '%%VERSION%%',
overlay: false,
wrapper: false,
defaults: {
timeout: 10,
disableClose: false,
icon: false,
className: 'achtung-default',
crossFadeMessage: 500, // 0 to disable
animateClassSwitch: 0, // 0 to disable (doesn't work with gradient backgrounds)
showEffects: {'opacity':'toggle'}, // ,'height':'toggle'},
hideEffects: {'opacity':'toggle'}, // ,'height':'toggle'},
showEffectDuration: 300,
hideEffectDuration: 500
}
});
/**
* Non-static members
**/
$.extend($.achtung.prototype, {
container: false,
icon: false,
message: false,
closeTimer: false,
options: {},
_init: function (args) {
var o, self = this;
o = this.options = $.extend.apply($, [{}, $.achtung.defaults].concat(args));
if ((o.animateClassSwitch > 0) && !('switchClass' in $.fn)) {
o.animateClassSwitch = this.options.animateClassSwitch = 0;
}
if (!o.disableClose) {
$('<span class="achtung-close-button ui-icon ui-icon-close" />')
.prependTo(this.container)
.bind({
click: function () { self.close(); }
});
}
this.changeIcon(o.icon, true);
if (o.message) {
this.message = $('<span>', {
'class': 'achtung-message',
html: o.message
}).appendTo(this.container);
}
if ('className' in o) {
this.container.addClass(o.className);
}
if ('css' in o) {
this.container.css(o.css);
}
if (!$.achtung.overlay) {
$.achtung.overlay = $('<div id="achtung-overlay"><div id="achtung-wrapper"></div></div>');
$.achtung.overlay.appendTo(document.body);
$.achtung.wrapper = $('#achtung-wrapper');
}
this.container.addClass('achtung').hide().appendTo($.achtung.wrapper);
if (o.showEffects) {
this.container.animate(o.showEffects, o.showEffectDuration);
} else {
this.container.show();
}
this.timeout(o.timeout);
},
timeout: function (timeout) {
var self = this;
if (this.closeTimer) {
clearTimeout(this.closeTimer);
}
if (timeout > 0) {
this.closeTimer = setTimeout(function () { self.close(); }, timeout * 1000);
this.options.timeout = timeout;
} else if (timeout < 0) {
this.close();
}
},
/**
* Change the CSS class associated with this message.
*
* @param newClass string Name of new class to associate
*/
changeClass: function (newClass) {
var oldClass = '' + this.options.className,
self = this;
if (oldClass === newClass) {
return;
}
this.container.queue(function (next) {
if (self.options.animateClassSwitch > 0) {
$(this).switchClass(oldClass, newClass, self.options.animateClassSwitch);
} else {
$(this).removeClass(oldClass).addClass(newClass);
}
next();
});
this.options.className = newClass;
},
changeIcon: function (newIcon, force) {
if (!force && this.options.icon === newIcon) {
return;
}
if (!!this.icon) {
if (newIcon) {
this.icon.removeClass(this.options.icon).addClass(newIcon);
} else {
this.icon.remove();
this.icon = false;
}
} else if (newIcon) {
this.icon = $('<span class="achtung-message-icon ui-icon ' + newIcon + '" />');
this.container.prepend(this.icon);
}
this.options.icon = newIcon;
},
changeMessage: function (newMessage) {
if (this.options.crossFadeMessage > 0) {
this.message.clone()
.css('position', 'absolute')
.insertBefore(this.message)
.fadeOut(this.options.crossFadeMessage, function () { $(this).remove(); });
this.message.hide().html(newMessage).fadeIn(this.options.crossFadeMessage);
} else {
this.message.html(newMessage);
}
this.options.message = newMessage;
},
update: function () {
var options = $.extend.apply($, [{}].concat(Array.prototype.slice.call(arguments, 0))),
map = {
className: 'changeClass',
css: 'css',
icon: 'changeIcon',
message: 'changeMessage',
timeout: 'timeout'
};
for (var prop in map) {
if (prop in options) {
this[map[prop]](options[prop]);
}
}
},
isVisible: function () {
return (true === this.container.is(':visible'));
},
_trigger: function (type, data) {
this.container.trigger(widgetName + type, data);
},
close: function () {
var o = this.options, self = this;
this._trigger('close');
if (o.hideEffects) {
this.container.animate(o.hideEffects, o.hideEffectDuration, function () {
self.remove();
});
} else {
this.container.hide();
this.remove();
}
},
remove: function () {
this.container.remove();
if ($.achtung.wrapper && !($.achtung.wrapper.contents().length)) {
$.achtung.wrapper = false;
$.achtung.overlay.remove();
$.achtung.overlay = false;
}
}
});
})(jQuery);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,736 @@
/*
Copyright (c) 2013, Fabien Meghazi
Released under the MIT license
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
// TODO: trim support
// TODO: line number -> https://bugzilla.mozilla.org/show_bug.cgi?id=618650
// TODO: templates orverwritten could be called by t-call="__super__" ?
// TODO: t-set + t-value + children node == scoped variable ?
(function() {
var QWeb2 = {
expressions_cache: {},
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': '&&',
'or': '||',
'gt': '>',
'gte': '>=',
'lt': '<',
'lte': '<='
},
tools: {
exception: function(message, context) {
context = context || {};
var prefix = 'QWeb2';
if (context.template) {
prefix += " - template['" + context.template + "']";
}
throw new Error(prefix + ": " + message);
},
warning : function(message) {
if (typeof(window) !== 'undefined' && window.console) {
window.console.warn(message);
}
},
trim: function(s, mode) {
switch (mode) {
case "left":
return s.replace(/^\s*/, "");
case "right":
return s.replace(/\s*$/, "");
default:
return s.replace(/^\s*|\s*$/g, "");
}
},
js_escape: function(s, noquotes) {
return (noquotes ? '' : "'") + s.replace(/\r?\n/g, "\\n").replace(/'/g, "\\'") + (noquotes ? '' : "'");
},
html_escape: function(s, attribute) {
if (s == null) {
return '';
}
s = String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
if (attribute) {
s = s.replace(/"/g, '&quot;');
}
return s;
},
gen_attribute: function(o) {
if (o !== null && o !== undefined) {
if (o.constructor === Array) {
if (o[1] !== null && o[1] !== undefined) {
return this.format_attribute(o[0], o[1]);
}
} else if (typeof o === 'object') {
var r = '';
for (var k in o) {
if (o.hasOwnProperty(k)) {
r += this.gen_attribute([k, o[k]]);
}
}
return r;
}
}
return '';
},
format_attribute: function(name, value) {
return ' ' + name + '="' + this.html_escape(value, true) + '"';
},
extend: function(dst, src, exclude) {
for (var p in src) {
if (src.hasOwnProperty(p) && !(exclude && this.arrayIndexOf(exclude, p) !== -1)) {
dst[p] = src[p];
}
}
return dst;
},
arrayIndexOf : function(array, item) {
for (var i = 0, ilen = array.length; i < ilen; i++) {
if (array[i] === item) {
return i;
}
}
return -1;
},
xml_node_to_string : function(node, childs_only) {
if (childs_only) {
var childs = node.childNodes, r = [];
for (var i = 0, ilen = childs.length; i < ilen; i++) {
r.push(this.xml_node_to_string(childs[i]));
}
return r.join('');
} else {
if (typeof XMLSerializer !== 'undefined') {
return (new XMLSerializer()).serializeToString(node);
} else {
switch(node.nodeType) {
case 1: return node.outerHTML;
case 3: return node.data;
case 4: return '<![CDATA[' + node.data + ']]>';
case 8: return '<!-- ' + node.data + '-->';
}
throw new Error('Unknown node type ' + node.nodeType);
}
}
},
call: function(context, template, old_dict, _import, callback) {
var new_dict = this.extend({}, old_dict);
new_dict['__caller__'] = old_dict['__template__'];
if (callback) {
new_dict['__content__'] = callback(context, new_dict);
}
var r = context.engine._render(template, new_dict);
if (_import) {
if (_import === '*') {
this.extend(old_dict, new_dict, ['__caller__', '__template__']);
} else {
_import = _import.split(',');
for (var i = 0, ilen = _import.length; i < ilen; i++) {
var v = _import[i];
old_dict[v] = new_dict[v];
}
}
}
return r;
},
foreach: function(context, enu, as, old_dict, callback) {
if (enu != null) {
var size, new_dict = this.extend({}, old_dict);
new_dict[as + "_all"] = enu;
var as_value = as + "_value",
as_index = as + "_index",
as_first = as + "_first",
as_last = as + "_last",
as_parity = as + "_parity";
if (size = enu.length) {
new_dict[as + "_size"] = size;
for (var j = 0, jlen = enu.length; j < jlen; j++) {
var cur = enu[j];
new_dict[as_value] = cur;
new_dict[as_index] = j;
new_dict[as_first] = j === 0;
new_dict[as_last] = j + 1 === size;
new_dict[as_parity] = (j % 2 == 1 ? 'odd' : 'even');
if (cur.constructor === Object) {
this.extend(new_dict, cur);
}
new_dict[as] = cur;
callback(context, new_dict);
}
} else if (enu.constructor == Number) {
var _enu = [];
for (var i = 0; i < enu; i++) {
_enu.push(i);
}
this.foreach(context, _enu, as, old_dict, callback);
} else {
var index = 0;
for (var k in enu) {
if (enu.hasOwnProperty(k)) {
var v = enu[k];
new_dict[as_value] = v;
new_dict[as_index] = index;
new_dict[as_first] = index === 0;
new_dict[as_parity] = (j % 2 == 1 ? 'odd' : 'even');
new_dict[as] = k;
callback(context, new_dict);
index += 1;
}
}
}
} else {
this.exception("No enumerator given to foreach", context);
}
}
}
};
QWeb2.Engine = (function() {
function Engine() {
// TODO: handle prefix at template level : t-prefix="x", don't forget to lowercase it
this.prefix = 't';
this.debug = false;
this.templates_resources = []; // TODO: implement this.reload()
this.templates = {};
this.compiled_templates = {};
this.extend_templates = {};
this.default_dict = {};
this.tools = QWeb2.tools;
this.jQuery = window.jQuery;
this.reserved_words = QWeb2.RESERVED_WORDS.slice(0);
this.actions_precedence = QWeb2.ACTIONS_PRECEDENCE.slice(0);
this.word_replacement = QWeb2.tools.extend({}, QWeb2.WORD_REPLACEMENT);
this.preprocess_node = null;
for (var i = 0; i < arguments.length; i++) {
this.add_template(arguments[i]);
}
}
QWeb2.tools.extend(Engine.prototype, {
add_template : function(template) {
this.templates_resources.push(template);
if (template.constructor === String) {
template = this.load_xml(template);
}
var ec = (template.documentElement && template.documentElement.childNodes) || template.childNodes || [];
for (var i = 0; i < ec.length; i++) {
var node = ec[i];
if (node.nodeType === 1) {
if (node.nodeName == 'parsererror') {
return this.tools.exception(node.innerText);
}
var name = node.getAttribute(this.prefix + '-name');
var extend = node.getAttribute(this.prefix + '-extend');
if (name && extend) {
// Clone template and extend it
if (!this.templates[extend]) {
return this.tools.exception("Can't clone undefined template " + extend);
}
this.templates[name] = this.templates[extend].cloneNode(true);
extend = name;
name = undefined;
}
if (name) {
this.templates[name] = node;
this.compiled_templates[name] = null;
} else if (extend) {
delete(this.compiled_templates[extend]);
if (this.extend_templates[extend]) {
this.extend_templates[extend].push(node);
} else {
this.extend_templates[extend] = [node];
}
}
}
}
return true;
},
load_xml : function(s) {
s = this.tools.trim(s);
if (s.charAt(0) === '<') {
return this.load_xml_string(s);
} else {
var req = this.get_xhr();
if (req) {
// TODO: third parameter is async : https://developer.mozilla.org/en/XMLHttpRequest#open()
// do an on_ready in QWeb2{} that could be passed to add_template
if (this.debug) {
s += '?debug=' + (new Date()).getTime(); // TODO fme: do it properly in case there's already url parameters
}
req.open('GET', s, false);
req.send(null);
var xDoc = req.responseXML;
if (xDoc) {
if (!xDoc.documentElement) {
throw new Error("QWeb2: This xml document has no root document : " + xDoc.responseText);
}
if (xDoc.documentElement.nodeName == "parsererror") {
return this.tools.exception(xDoc.documentElement.childNodes[0].nodeValue);
}
return xDoc;
} else {
return this.load_xml_string(req.responseText);
}
}
}
},
load_xml_string : function(s) {
if (window.DOMParser) {
var dp = new DOMParser();
var r = dp.parseFromString(s, "text/xml");
if (r.body && r.body.firstChild && r.body.firstChild.nodeName == 'parsererror') {
return this.tools.exception(r.body.innerText);
}
return r;
}
var xDoc;
try {
// new ActiveXObject("Msxml2.DOMDocument.4.0");
xDoc = new ActiveXObject("MSXML2.DOMDocument");
} catch (e) {
return this.tools.exception(
"Could not find a DOM Parser: " + e.message);
}
xDoc.async = false;
xDoc.preserveWhiteSpace = true;
xDoc.loadXML(s);
return xDoc;
},
has_template : function(template) {
return !!this.templates[template];
},
get_xhr : function() {
if (window.XMLHttpRequest) {
return new window.XMLHttpRequest();
}
try {
return new ActiveXObject('MSXML2.XMLHTTP.3.0');
} catch (e) {
return null;
}
},
compile : function(node) {
var e = new QWeb2.Element(this, node);
var template = node.getAttribute(this.prefix + '-name');
return " /* 'this' refers to Qweb2.Engine instance */\n" +
" var context = { engine : this, template : " + (this.tools.js_escape(template)) + " };\n" +
" dict = dict || {};\n" +
" dict['__template__'] = '" + template + "';\n" +
" var r = [];\n" +
" /* START TEMPLATE */" +
(this.debug ? "" : " try {\n") +
(e.compile()) + "\n" +
" /* END OF TEMPLATE */" +
(this.debug ? "" : " } catch(error) {\n" +
" if (console && console.exception) console.exception(error);\n" +
" context.engine.tools.exception('Runtime Error: ' + error, context);\n") +
(this.debug ? "" : " }\n") +
" return r.join('');";
},
render : function(template, dict) {
dict = dict || {};
QWeb2.tools.extend(dict, this.default_dict);
/*if (this.debug && window['console'] !== undefined) {
console.time("QWeb render template " + template);
}*/
var r = this._render(template, dict);
/*if (this.debug && window['console'] !== undefined) {
console.timeEnd("QWeb render template " + template);
}*/
return r;
},
_render : function(template, dict) {
if (this.compiled_templates[template]) {
return this.compiled_templates[template].apply(this, [dict || {}]);
} else if (this.templates[template]) {
var ext;
if (ext = this.extend_templates[template]) {
var extend_node;
while (extend_node = ext.shift()) {
this.extend(template, extend_node);
}
}
var code = this.compile(this.templates[template]), tcompiled;
try {
tcompiled = new Function(['dict'], code);
} catch (error) {
if (this.debug && window.console) {
console.log(code);
}
this.tools.exception("Error evaluating template: " + error, { template: name });
}
if (!tcompiled) {
this.tools.exception("Error evaluating template: (IE?)" + error, { template: name });
}
this.compiled_templates[template] = tcompiled;
return this.render(template, dict);
} else {
return this.tools.exception("Template '" + template + "' not found");
}
},
extend : function(template, extend_node) {
if (!this.jQuery) {
return this.tools.exception("Can't extend template " + template + " without jQuery");
}
var template_dest = this.templates[template];
for (var i = 0, ilen = extend_node.childNodes.length; i < ilen; i++) {
var child = extend_node.childNodes[i];
if (child.nodeType === 1) {
var jquery = child.getAttribute(this.prefix + '-jquery'),
operation = child.getAttribute(this.prefix + '-operation'),
target,
error_msg = "Error while extending template '" + template;
if (jquery) {
target = this.jQuery(jquery, template_dest);
} else {
this.tools.exception(error_msg + "No expression given");
}
error_msg += "' (expression='" + jquery + "') : ";
if (operation) {
var allowed_operations = "append,prepend,before,after,replace,inner".split(',');
if (this.tools.arrayIndexOf(allowed_operations, operation) == -1) {
this.tools.exception(error_msg + "Invalid operation : '" + operation + "'");
}
operation = {'replace' : 'replaceWith', 'inner' : 'html'}[operation] || operation;
target[operation](child.cloneNode(true).childNodes);
} else {
try {
var f = new Function(['$', 'document'], this.tools.xml_node_to_string(child, true));
} catch(error) {
return this.tools.exception("Parse " + error_msg + error);
}
try {
f.apply(target, [this.jQuery, template_dest.ownerDocument]);
} catch(error) {
return this.tools.exception("Runtime " + error_msg + error);
}
}
}
}
}
});
return Engine;
})();
QWeb2.Element = (function() {
function Element(engine, node) {
this.engine = engine;
this.node = node;
this.tag = node.tagName;
this.actions = {};
this.actions_done = [];
this.attributes = {};
this.children = [];
this._top = [];
this._bottom = [];
this._indent = 1;
this.process_children = true;
var childs = this.node.childNodes;
if (childs) {
for (var i = 0, ilen = childs.length; i < ilen; i++) {
this.children.push(new QWeb2.Element(this.engine, childs[i]));
}
}
var attrs = this.node.attributes;
if (attrs) {
for (var j = 0, jlen = attrs.length; j < jlen; j++) {
var attr = attrs[j];
var name = attr.name;
var m = name.match(new RegExp("^" + this.engine.prefix + "-(.+)"));
if (m) {
name = m[1];
if (name === 'name') {
continue;
}
this.actions[name] = attr.value;
} else {
this.attributes[name] = attr.value;
}
}
}
if (this.engine.preprocess_node) {
this.engine.preprocess_node.call(this);
}
}
QWeb2.tools.extend(Element.prototype, {
compile : function() {
var r = [],
instring = false,
lines = this._compile().split('\n');
for (var i = 0, ilen = lines.length; i < ilen; i++) {
var m, line = lines[i];
if (m = line.match(/^(\s*)\/\/@string=(.*)/)) {
if (instring) {
if (this.engine.debug) {
// Split string lines in indented r.push arguments
r.push((m[2].indexOf("\\n") != -1 ? "',\n\t" + m[1] + "'" : '') + m[2]);
} else {
r.push(m[2]);
}
} else {
r.push(m[1] + "r.push('" + m[2]);
instring = true;
}
} else {
if (instring) {
r.push("');\n");
}
instring = false;
r.push(line + '\n');
}
}
return r.join('');
},
_compile : function() {
switch (this.node.nodeType) {
case 3:
case 4:
this.top_string(this.node.data);
break;
case 1:
this.compile_element();
}
var r = this._top.join('');
if (this.process_children) {
for (var i = 0, ilen = this.children.length; i < ilen; i++) {
var child = this.children[i];
child._indent = this._indent;
r += child._compile();
}
}
r += this._bottom.join('');
return r;
},
format_expression : function(e) {
/* Naive format expression builder. Replace reserved words and variables to dict[variable]
* Does not handle spaces before dot yet, and causes problems for anonymous functions. Use t-js="" for that */
if (QWeb2.expressions_cache[e]) {
return QWeb2.expressions_cache[e];
}
var chars = e.split(''),
instring = '',
invar = '',
invar_pos = 0,
r = '';
chars.push(' ');
for (var i = 0, ilen = chars.length; i < ilen; i++) {
var c = chars[i];
if (instring.length) {
if (c === instring && chars[i - 1] !== "\\") {
instring = '';
}
} else if (c === '"' || c === "'") {
instring = c;
} else if (c.match(/[a-zA-Z_\$]/) && !invar.length) {
invar = c;
invar_pos = i;
continue;
} else if (c.match(/\W/) && invar.length) {
// TODO: Should check for possible spaces before dot
if (chars[invar_pos - 1] !== '.' && QWeb2.tools.arrayIndexOf(this.engine.reserved_words, invar) < 0) {
invar = this.engine.word_replacement[invar] || ("dict['" + invar + "']");
}
r += invar;
invar = '';
} else if (invar.length) {
invar += c;
continue;
}
r += c;
}
r = r.slice(0, -1);
QWeb2.expressions_cache[e] = r;
return r;
},
string_interpolation : function(s) {
if (!s) {
return "''";
}
var regex = /^{(.*)}(.*)/,
src = s.split(/#/),
r = [];
for (var i = 0, ilen = src.length; i < ilen; i++) {
var val = src[i],
m = val.match(regex);
if (m) {
r.push("(" + this.format_expression(m[1]) + ")");
if (m[2]) {
r.push(this.engine.tools.js_escape(m[2]));
}
} else if (!(i === 0 && val === '')) {
r.push(this.engine.tools.js_escape((i === 0 ? '' : '#') + val));
}
}
return r.join(' + ');
},
indent : function() {
return this._indent++;
},
dedent : function() {
if (this._indent !== 0) {
return this._indent--;
}
},
get_indent : function() {
return new Array(this._indent + 1).join("\t");
},
top : function(s) {
return this._top.push(this.get_indent() + s + '\n');
},
top_string : function(s) {
return this._top.push(this.get_indent() + "//@string=" + this.engine.tools.js_escape(s, true) + '\n');
},
bottom : function(s) {
return this._bottom.unshift(this.get_indent() + s + '\n');
},
bottom_string : function(s) {
return this._bottom.unshift(this.get_indent() + "//@string=" + this.engine.tools.js_escape(s, true) + '\n');
},
compile_element : function() {
for (var i = 0, ilen = this.engine.actions_precedence.length; i < ilen; i++) {
var a = this.engine.actions_precedence[i];
if (a in this.actions) {
var value = this.actions[a];
var key = 'compile_action_' + a;
if (this[key]) {
this[key](value);
} else if (this.engine[key]) {
this.engine[key].call(this, value);
} else {
this.engine.tools.exception("No handler method for action '" + a + "'");
}
}
}
if (this.tag.toLowerCase() !== this.engine.prefix) {
var tag = "<" + this.tag;
for (var a in this.attributes) {
tag += this.engine.tools.gen_attribute([a, this.attributes[a]]);
}
this.top_string(tag);
if (this.actions.att) {
this.top("r.push(context.engine.tools.gen_attribute(" + (this.format_expression(this.actions.att)) + "));");
}
for (var a in this.actions) {
var v = this.actions[a];
var m = a.match(/att-(.+)/);
if (m) {
this.top("r.push(context.engine.tools.gen_attribute(['" + m[1] + "', (" + (this.format_expression(v)) + ")]));");
}
var m = a.match(/attf-(.+)/);
if (m) {
this.top("r.push(context.engine.tools.gen_attribute(['" + m[1] + "', (" + (this.string_interpolation(v)) + ")]));");
}
}
if (this.children.length || this.actions.opentag === 'true') {
this.top_string(">");
this.bottom_string("</" + this.tag + ">");
} else {
this.top_string("/>");
}
}
},
compile_action_if : function(value) {
this.top("if (" + (this.format_expression(value)) + ") {");
this.bottom("}");
this.indent();
},
compile_action_foreach : function(value) {
var as = this.actions['as'] || value.replace(/[^a-zA-Z0-9]/g, '_');
//TODO: exception if t-as not valid
this.top("context.engine.tools.foreach(context, " + (this.format_expression(value)) + ", " + (this.engine.tools.js_escape(as)) + ", dict, function(context, dict) {");
this.bottom("});");
this.indent();
},
compile_action_call : function(value) {
var _import = this.actions['import'] || '';
if (this.children.length === 0) {
return this.top("r.push(context.engine.tools.call(context, " + (this.engine.tools.js_escape(value)) + ", dict, " + (this.engine.tools.js_escape(_import)) + "));");
} else {
this.top("r.push(context.engine.tools.call(context, " + (this.engine.tools.js_escape(value)) + ", dict, " + (this.engine.tools.js_escape(_import)) + ", function(context, dict) {");
this.bottom("}));");
this.indent();
this.top("var r = [];");
return this.bottom("return r.join('');");
}
},
compile_action_set : function(value) {
var variable = this.format_expression(value);
if (this.actions['value']) {
if (this.children.length) {
this.engine.tools.warning("@set with @value plus node chidren found. Children are ignored.");
}
this.top(variable + " = (" + (this.format_expression(this.actions['value'])) + ");");
this.process_children = false;
} else {
if (this.children.length === 0) {
this.top(variable + " = '';");
} else if (this.children.length === 1 && this.children[0].node.nodeType === 3) {
this.top(variable + " = " + (this.engine.tools.js_escape(this.children[0].node.data)) + ";");
this.process_children = false;
} else {
this.top(variable + " = (function(dict) {");
this.bottom("})(dict);");
this.indent();
this.top("var r = [];");
this.bottom("return r.join('');");
}
}
},
compile_action_esc : function(value) {
this.top("r.push(context.engine.tools.html_escape(" + (this.format_expression(value)) + "));");
},
compile_action_escf : function(value) {
this.top("r.push(context.engine.tools.html_escape(" + (this.string_interpolation(value)) + "));");
},
compile_action_raw : function(value) {
this.top("r.push(" + (this.format_expression(value)) + ");");
},
compile_action_rawf : function(value) {
this.top("r.push(" + (this.string_interpolation(value)) + ");");
},
compile_action_js : function(value) {
this.top("(function(" + value + ") {");
this.bottom("})(dict);");
this.indent();
var lines = this.engine.tools.xml_node_to_string(this.node, true).split(/\r?\n/);
for (var i = 0, ilen = lines.length; i < ilen; i++) {
this.top(lines[i]);
}
this.process_children = false;
},
compile_action_debug : function(value) {
this.top("debugger;");
},
compile_action_log : function(value) {
this.top("console.log(" + this.format_expression(value) + ");");
}
});
return Element;
})();
window.QWeb2 = QWeb2;
})();
@@ -1,18 +1,18 @@
/** vim: et:ts=4:sw=4:sts=4
* @license RequireJS 2.1.2 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
* @license RequireJS 2.1.8 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
* Available via the MIT or new BSD license.
* see: http://github.com/jrburke/requirejs for details
*/
//Not using strict: uneven strict support in browsers, #392, and causes
//problems with requirejs.exec()/transpiler plugins that may not be strict.
/*jslint regexp: true, nomen: true, sloppy: true */
/*global window, navigator, document, importScripts, jQuery, setTimeout, opera */
/*global window, navigator, document, importScripts, setTimeout, opera */
var requirejs, require, define;
(function (global) {
var req, s, head, baseElement, dataMain, src,
interactiveScript, currentlyAddingScript, mainScript, subPath,
version = '2.1.2',
version = '2.1.8',
commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,
cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,
jsSuffixRegExp = /\.js$/,
@@ -21,9 +21,8 @@ var requirejs, require, define;
ostring = op.toString,
hasOwn = op.hasOwnProperty,
ap = Array.prototype,
aps = ap.slice,
apsp = ap.splice,
isBrowser = !!(typeof window !== 'undefined' && navigator && document),
isBrowser = !!(typeof window !== 'undefined' && navigator && window.document),
isWebWorker = !isBrowser && typeof importScripts !== 'undefined',
//PS3 indicates loaded and complete, but need to wait for complete
//specifically. Sequence is 'loading', 'loaded', execution,
@@ -135,6 +134,10 @@ var requirejs, require, define;
return document.getElementsByTagName('script');
}
function defaultOnError(err) {
throw err;
}
//Allow getting a global that expressed in
//dot notation, like 'a.b.c'.
function getGlobal(value) {
@@ -192,15 +195,21 @@ var requirejs, require, define;
var inCheckLoaded, Module, context, handlers,
checkLoadedTimeoutId,
config = {
//Defaults. Do not set a default for map
//config to speed up normalize(), which
//will run faster if there is no default.
waitSeconds: 7,
baseUrl: './',
paths: {},
pkgs: {},
shim: {},
map: {},
config: {}
},
registry = {},
//registry of just enabled modules, to speed
//cycle breaking code when lots of modules
//are registered, but not activated.
enabledRegistry = {},
undefEvents = {},
defQueue = [],
defined = {},
@@ -296,7 +305,7 @@ var requirejs, require, define;
}
//Apply map config if available.
if (applyMap && (baseParts || starMap) && map) {
if (applyMap && map && (baseParts || starMap)) {
nameParts = name.split('/');
for (i = nameParts.length; i > 0; i -= 1) {
@@ -495,7 +504,12 @@ var requirejs, require, define;
fn(defined[id]);
}
} else {
getModule(depMap).on(name, fn);
mod = getModule(depMap);
if (mod.error && name === 'error') {
fn(mod.error);
} else {
mod.on(name, fn);
}
}
}
@@ -566,7 +580,13 @@ var requirejs, require, define;
id: mod.map.id,
uri: mod.map.url,
config: function () {
return (config.config && getOwn(config.config, mod.map.id)) || {};
var c,
pkg = getOwn(config.pkgs, mod.map.id);
// For packages, only support config targeted
// at the main module.
c = pkg ? getOwn(config.config, mod.map.id + '/' + pkg.main) :
getOwn(config.config, mod.map.id);
return c || {};
},
exports: defined[mod.map.id]
});
@@ -577,6 +597,7 @@ var requirejs, require, define;
function cleanRegistry(id) {
//Clean up machinery used for waiting modules.
delete registry[id];
delete enabledRegistry[id];
}
function breakCycle(mod, traced, processed) {
@@ -625,7 +646,7 @@ var requirejs, require, define;
inCheckLoaded = true;
//Figure out the state of all the modules.
eachProp(registry, function (mod) {
eachProp(enabledRegistry, function (mod) {
map = mod.map;
modId = map.id;
@@ -806,7 +827,7 @@ var requirejs, require, define;
},
/**
* Checks is the module is ready to define itself, and if so,
* Checks if the module is ready to define itself, and if so,
* define it.
*/
check: function () {
@@ -834,8 +855,13 @@ var requirejs, require, define;
if (this.depCount < 1 && !this.defined) {
if (isFunction(factory)) {
//If there is an error listener, favor passing
//to that instead of throwing an error.
if (this.events.error) {
//to that instead of throwing an error. However,
//only do it for define()'d modules. require
//errbacks should not be called for failures in
//their callbacks (#699). However if a global
//onError is set, use that.
if ((this.events.error && this.map.isDefine) ||
req.onError !== defaultOnError) {
try {
exports = context.execCb(id, factory, depExports, exports);
} catch (e) {
@@ -863,8 +889,8 @@ var requirejs, require, define;
if (err) {
err.requireMap = this.map;
err.requireModules = [this.map.id];
err.requireType = 'define';
err.requireModules = this.map.isDefine ? [this.map.id] : null;
err.requireType = this.map.isDefine ? 'define' : 'require';
return onError((this.error = err));
}
@@ -884,7 +910,7 @@ var requirejs, require, define;
}
//Clean up
delete registry[id];
cleanRegistry(id);
this.defined = true;
}
@@ -918,8 +944,7 @@ var requirejs, require, define;
name = this.map.name,
parentName = this.map.parentMap ? this.map.parentMap.name : null,
localRequire = context.makeRequire(map.parentMap, {
enableBuildCallback: true,
skipMap: true
enableBuildCallback: true
});
//If current map is not normalized, wait for that
@@ -1017,8 +1042,11 @@ var requirejs, require, define;
try {
req.exec(text);
} catch (e) {
throw new Error('fromText eval for ' + moduleName +
' failed: ' + e);
return onError(makeError('fromtexteval',
'fromText eval for ' + id +
' failed: ' + e,
e,
[id]));
}
if (hasInteractive) {
@@ -1048,6 +1076,7 @@ var requirejs, require, define;
},
enable: function () {
enabledRegistry[this.map.id] = this;
this.enabled = true;
//Set flag mentioning that the module is enabling,
@@ -1084,7 +1113,7 @@ var requirejs, require, define;
}));
if (this.errback) {
on(depMap, 'error', this.errback);
on(depMap, 'error', bind(this, this.errback));
}
}
@@ -1207,6 +1236,7 @@ var requirejs, require, define;
Module: Module,
makeModuleMap: makeModuleMap,
nextTick: req.nextTick,
onError: onError,
/**
* Set a configuration for the context.
@@ -1233,6 +1263,9 @@ var requirejs, require, define;
eachProp(cfg, function (value, prop) {
if (objs[prop]) {
if (prop === 'map') {
if (!config.map) {
config.map = {};
}
mixin(config[prop], value, true, true);
} else {
mixin(config[prop], value, true);
@@ -1344,7 +1377,7 @@ var requirejs, require, define;
//Synchronous access to one module. If require.get is
//available (as in the Node adapter), prefer that.
if (req.get) {
return req.get(context, deps, relMap);
return req.get(context, deps, relMap, localRequire);
}
//Normalize module name, if it contains . or ..
@@ -1395,16 +1428,20 @@ var requirejs, require, define;
* plain URLs like nameToUrl.
*/
toUrl: function (moduleNamePlusExt) {
var index = moduleNamePlusExt.lastIndexOf('.'),
ext = null;
var ext,
index = moduleNamePlusExt.lastIndexOf('.'),
segment = moduleNamePlusExt.split('/')[0],
isRelative = segment === '.' || segment === '..';
if (index !== -1) {
//Have a file extension alias, and it is not the
//dots from a relative path.
if (index !== -1 && (!isRelative || index > 1)) {
ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length);
moduleNamePlusExt = moduleNamePlusExt.substring(0, index);
}
return context.nameToUrl(normalize(moduleNamePlusExt,
relMap && relMap.id, true), ext);
relMap && relMap.id, true), ext, true);
},
defined: function (id) {
@@ -1449,10 +1486,11 @@ var requirejs, require, define;
/**
* Called to enable a module if it is still in the registry
* awaiting enablement. parent module is passed in for context,
* used by the optimizer.
* awaiting enablement. A second arg, parent, the parent module,
* is passed in for context, when this method is overriden by
* the optimizer. Not shown here to keep code compact.
*/
enable: function (depMap, parent) {
enable: function (depMap) {
var mod = getOwn(registry, depMap.id);
if (mod) {
getModule(depMap).enable();
@@ -1522,7 +1560,7 @@ var requirejs, require, define;
* it is assumed to have already been normalized. This is an
* internal API, not a public one. Use toUrl for the public API.
*/
nameToUrl: function (moduleName, ext) {
nameToUrl: function (moduleName, ext, skipExt) {
var paths, pkgs, pkg, pkgPath, syms, i, parentModule, url,
parentPath;
@@ -1571,7 +1609,7 @@ var requirejs, require, define;
//Join the path parts together, then figure out if baseUrl is needed.
url = syms.join('/');
url += (ext || (/\?/.test(url) ? '' : '.js'));
url += (ext || (/\?/.test(url) || skipExt ? '' : '.js'));
url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url;
}
@@ -1587,7 +1625,7 @@ var requirejs, require, define;
},
/**
* Executes a module callack function. Broken out as a separate function
* Executes a module callback function. Broken out as a separate function
* solely to allow the build system to sequence the files in the built
* layer in the right sequence.
*
@@ -1625,7 +1663,7 @@ var requirejs, require, define;
onScriptError: function (evt) {
var data = getScriptData(evt);
if (!hasPathFallback(data.id)) {
return onError(makeError('scripterror', 'Script error', evt, [data.id]));
return onError(makeError('scripterror', 'Script error for: ' + data.id, evt, [data.id]));
}
}
};
@@ -1754,8 +1792,19 @@ var requirejs, require, define;
* function. Intercept/override it if you want custom error handling.
* @param {Error} err the error object.
*/
req.onError = function (err) {
throw err;
req.onError = defaultOnError;
/**
* Creates the node for the load command. Only used in browser envs.
*/
req.createNode = function (config, moduleName, url) {
var node = config.xhtml ?
document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') :
document.createElement('script');
node.type = config.scriptType || 'text/javascript';
node.charset = 'utf-8';
node.async = true;
return node;
};
/**
@@ -1772,12 +1821,7 @@ var requirejs, require, define;
node;
if (isBrowser) {
//In the browser so use a script tag
node = config.xhtml ?
document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') :
document.createElement('script');
node.type = config.scriptType || 'text/javascript';
node.charset = 'utf-8';
node.async = true;
node = req.createNode(config, moduleName, url);
node.setAttribute('data-requirecontext', context.contextName);
node.setAttribute('data-requiremodule', moduleName);
@@ -1810,7 +1854,7 @@ var requirejs, require, define;
node.attachEvent('onreadystatechange', context.onScriptLoad);
//It would be great to add an error handler here to catch
//404s in IE9+. However, onreadystatechange will fire before
//the error handler, so that does not help. If addEvenListener
//the error handler, so that does not help. If addEventListener
//is used, then IE will fire error before load, but we cannot
//use that pathway given the connect.microsoft.com issue
//mentioned above about not doing the 'script execute,
@@ -1839,16 +1883,24 @@ var requirejs, require, define;
return node;
} else if (isWebWorker) {
//In a web worker, use importScripts. This is not a very
//efficient use of importScripts, importScripts will block until
//its script is downloaded and evaluated. However, if web workers
//are in play, the expectation that a build has been done so that
//only one script needs to be loaded anyway. This may need to be
//reevaluated if other use cases become common.
importScripts(url);
try {
//In a web worker, use importScripts. This is not a very
//efficient use of importScripts, importScripts will block until
//its script is downloaded and evaluated. However, if web workers
//are in play, the expectation that a build has been done so that
//only one script needs to be loaded anyway. This may need to be
//reevaluated if other use cases become common.
importScripts(url);
//Account for anonymous modules
context.completeLoad(moduleName);
//Account for anonymous modules
context.completeLoad(moduleName);
} catch (e) {
context.onError(makeError('importscripts',
'importScripts failed for ' +
moduleName + ' at ' + url,
e,
[moduleName]));
}
}
};
@@ -1880,24 +1932,31 @@ var requirejs, require, define;
//baseUrl, if it is not already set.
dataMain = script.getAttribute('data-main');
if (dataMain) {
//Preserve dataMain in case it is a path (i.e. contains '?')
mainScript = dataMain;
//Set final baseUrl if there is not already an explicit one.
if (!cfg.baseUrl) {
//Pull off the directory of data-main for use as the
//baseUrl.
src = dataMain.split('/');
src = mainScript.split('/');
mainScript = src.pop();
subPath = src.length ? src.join('/') + '/' : './';
cfg.baseUrl = subPath;
dataMain = mainScript;
}
//Strip off any trailing .js since dataMain is now
//Strip off any trailing .js since mainScript is now
//like a module name.
dataMain = dataMain.replace(jsSuffixRegExp, '');
mainScript = mainScript.replace(jsSuffixRegExp, '');
//If mainScript is still a path, fall back to dataMain
if (req.jsExtRegExp.test(mainScript)) {
mainScript = dataMain;
}
//Put the data-main script in the files to load.
cfg.deps = cfg.deps ? cfg.deps.concat(dataMain) : [dataMain];
cfg.deps = cfg.deps ? cfg.deps.concat(mainScript) : [mainScript];
return true;
}
@@ -1925,12 +1984,13 @@ var requirejs, require, define;
//This module may not have dependencies
if (!isArray(deps)) {
callback = deps;
deps = [];
deps = null;
}
//If no name, and callback is a function, then figure out if it a
//CommonJS thing with dependencies.
if (!deps.length && isFunction(callback)) {
if (!deps && isFunction(callback)) {
deps = [];
//Remove comments from the callback string,
//look for require calls, and pull them into the dependencies,
//but only if there are function args.
@@ -1990,4 +2050,4 @@ var requirejs, require, define;
//Set up with config info.
req(cfg);
}(this));
}(this));
+2 -2
View File
@@ -1,8 +1,8 @@
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="http://localhost/im_livechat/static/ext/static/js/require.js"></script>
<script type="text/javascript" src='http://localhost/im_livechat/loader?p={"db":"test","channel":1}'></script>
<script type="text/javascript" src="http://localhost/im_livechat/static/ext/static/lib/requirejs/require.js"></script>
<script type="text/javascript" src='http://localhost/im_livechat/loader?p={"db":"testtrunk","channel":1}'></script>
</head>
<body style="height:100%; margin:0; padding:0;">
<iframe src="http://openerp.com" height="100%" width=100%"></iframe>