[MERGE] Merged lp:~openerp-dev/openobject-addons/trunk-website-al

bzr revid: psa@tinyerp.com-20131111063123-qysh7oyiscfy8ku9
This commit is contained in:
Paramjit Singh Sahota 2013-11-11 12:01:23 +05:30
commit be9553d6f1
85 changed files with 4531 additions and 2732 deletions

View File

@ -155,7 +155,6 @@
<field name="type" ref="event_type_4"/>
<field name="user_id" ref="base.user_root"/>
<field name="address_id" ref="base.res_partner_2"/>
<field name="organizer_id" ref="base.res_partner_address_4"/>
<field name="description"><![CDATA[
<div class="oe_structure">
<center><strong>5-days Technical Training</strong></center>

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
import base64
import cStringIO
import contextlib
import hashlib
@ -7,7 +6,6 @@ import json
import logging
import os
import datetime
import re
from sys import maxint
@ -18,14 +16,6 @@ import werkzeug.utils
import werkzeug.wrappers
from PIL import Image
try:
from slugify import slugify
except ImportError:
def slugify(s, max_length=None):
spaceless = re.sub(r'\s+', '-', s)
specialless = re.sub(r'[^-_a-z0-9]', '', spaceless)
return specialless[:max_length]
import openerp
from openerp.osv import fields
from openerp.addons.website.models import website
@ -38,7 +28,7 @@ logger = logging.getLogger(__name__)
def auth_method_public():
registry = openerp.modules.registry.RegistryManager.get(request.db)
if not request.session.uid:
request.uid = registry['website'].get_public_user().id
request.uid = registry['website'].get_public_user(request.cr, openerp.SUPERUSER_ID, request.context).id
else:
request.uid = request.session.uid
http.auth_methods['public'] = auth_method_public
@ -49,13 +39,25 @@ MAX_IMAGE_WIDTH, MAX_IMAGE_HEIGHT = IMAGE_LIMITS = (1024, 768)
class Website(openerp.addons.web.controllers.main.Home):
@website.route('/', type='http', auth="public", multilang=True)
def index(self, **kw):
return self.page("website.homepage")
# TODO: check if plain SQL is needed
menu = request.registry['website.menu']
root_domain = [('parent_id', '=', False)] # TODO: multiwebsite ('website_id', '=', request.website.id),
root_id = menu.search(request.cr, request.uid, root_domain, limit=1, context=request.context)[0]
first_menu = menu.search_read(
request.cr, request.uid, [('parent_id', '=', root_id)], ['url'],
limit=1, order='sequence', context=request.context)
if first_menu:
first_menu = first_menu[0]['url']
if first_menu and first_menu != '/':
return request.redirect(first_menu)
else:
return self.page("website.homepage")
@website.route('/pagenew/<path:path>', type='http', auth="user")
def pagenew(self, path, noredirect=NOPE):
module = 'website'
# completely arbitrary max_length
idname = slugify(path, max_length=50)
idname = http.slugify(path, max_length=50)
request.cr.execute('SAVEPOINT pagenew')
imd = request.registry['ir.model.data']
@ -124,11 +126,8 @@ class Website(openerp.addons.web.controllers.main.Home):
values = {
'path': path,
}
try:
html = request.website.render(path, values)
except ValueError:
html = request.website.render('website.404', values)
return html
return request.website.render(path, values)
@website.route('/website/customize_template_toggle', type='json', auth='user')
def customize_template_set(self, view_id):
@ -269,12 +268,24 @@ class Website(openerp.addons.web.controllers.main.Home):
@website.route(['/robots.txt'], type='http', auth="public")
def robots(self):
return request.website.render('website.robots', {'url_root': request.httprequest.url_root})
body = request.website.render('website.robots', {'url_root': request.httprequest.url_root})
return request.make_response(body, headers=[('Content-Type', 'text/plain')])
@website.route(['/sitemap.xml'], type='http', auth="public")
@website.route('/sitemap', type='http', auth='public', multilang=True)
def sitemap(self):
return request.website.render('website.sitemap', {'pages': request.website.list_pages()})
@website.route('/sitemap.xml', type='http', auth="public")
def sitemap_xml(self):
body = request.website.render('website.sitemap_xml', {
'pages': request.website.list_pages()
})
return request.make_response(body, [
('Content-Type', 'application/xml;charset=utf-8')
])
class Images(http.Controller):
def placeholder(self, response):
# file_open may return a StringIO. StringIO can be closed but are

View File

@ -8,6 +8,24 @@
<field name="default_lang_id" ref="base.lang_en"/>
</record>
<record id="main_menu" model="website.menu">
<field name="name">Top Menu</field>
</record>
<record id="menu_homepage" model="website.menu">
<field name="name">Home</field>
<field name="url">/</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence" type="int">10</field>
</record>
<record id="menu_contactus" model="website.menu">
<field name="name">Contact us</field>
<field name="url">/page/website.contactus</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence" type="int">20</field>
</record>
<record id="public_user" model="res.users">
<field name="name">public</field>
<field name="login">public</field>

View File

@ -9,5 +9,8 @@
<field name="social_linkedin">http://www.linkedin.com/company/openerp</field>
</record>
<record id="base.main_company" model="res.company">
<field name="rml_header1">Great Product for Great People</field>
</record>
</data>
</openerp>

View File

@ -131,7 +131,9 @@ class view(osv.osv):
arch_section = html.fromstring(
value, parser=html.HTMLParser(encoding='utf-8'))
self._normalize_urls(arch_section)
# TODO fme: Temporary desactivated because this breaks most of the snippets
# Need to find another way to normalize multilang urls (postprocessing) ?
# self._normalize_urls(arch_section)
if xpath is None:
# value is an embedded field on its own, not a view section

View File

@ -1,27 +1,34 @@
# -*- coding: utf-8 -*-
import fnmatch
import functools
import inspect
import logging
import math
import itertools
import traceback
import urllib
import urlparse
import simplejson
import werkzeug
import werkzeug.exceptions
import werkzeug.wrappers
import openerp
from openerp.osv import osv, fields
from openerp.exceptions import AccessError, AccessDenied
from openerp.osv import orm, osv, fields
from openerp.tools.safe_eval import safe_eval
from openerp.addons.web import http
from openerp.addons.web.http import request
import urllib
from urlparse import urljoin
import math
import traceback
from openerp.tools.safe_eval import safe_eval
from openerp.exceptions import AccessError, AccessDenied
import werkzeug
from openerp.addons.base.ir.ir_qweb import QWebException
import logging
logger = logging.getLogger(__name__)
def route(routes, *route_args, **route_kwargs):
def decorator(f):
new_routes = routes if isinstance(routes, list) else [routes]
f.cms = True
f.multilang = route_kwargs.get('multilang', False)
if f.multilang:
route_kwargs.pop('multilang')
@ -33,47 +40,46 @@ def route(routes, *route_args, **route_kwargs):
request.route_lang = kwargs.get('lang_code', None)
if not hasattr(request, 'website'):
request.multilang = f.multilang
request.website = request.registry['website'].get_current()
# TODO: Select website, currently hard coded
request.website = request.registry['website'].browse(
request.cr, request.uid, 1, context=request.context)
if request.route_lang:
lang_ok = [lg.code for lg in request.website.language_ids if lg.code == request.route_lang]
if not lang_ok:
return request.not_found()
request.website.preprocess_request(*args, **kwargs)
request.website.preprocess_request(request)
return f(*args, **kwargs)
return wrap
return decorator
def auth_method_public():
registry = openerp.modules.registry.RegistryManager.get(request.db)
if not request.session.uid:
request.uid = registry['website'].get_public_user().id
else:
request.uid = request.session.uid
http.auth_methods['public'] = auth_method_public
def url_for(path, lang=None, keep_query=None):
if request:
path = urljoin(request.httprequest.path, path)
def url_for(path_or_uri, lang=None, keep_query=None):
location = path_or_uri.strip()
url = urlparse.urlparse(location)
if request and not url.netloc and not url.scheme:
location = urlparse.urljoin(request.httprequest.path, location)
langs = request.context.get('langs')
if path[0] == '/' and len(langs) > 1:
ps = path.split('/')
if location[0] == '/' and (len(langs) > 1 or lang):
ps = location.split('/')
lang = lang or request.context.get('lang')
if ps[1] in langs:
ps[1] = lang
else:
ps.insert(1, lang)
path = '/'.join(ps)
location = '/'.join(ps)
if keep_query:
keep = []
params = werkzeug.url_decode(request.httprequest.query_string)
params_keys = tuple(params.keys())
url = urlparse.urlparse(location)
location = url.path
params = werkzeug.url_decode(url.query)
query_params = frozenset(werkzeug.url_decode(request.httprequest.query_string).keys())
for kq in keep_query:
keep += fnmatch.filter(params_keys, kq)
if keep:
params = dict([(k, params[k]) for k in keep])
path += u'?%s' % werkzeug.urls.url_encode(params)
for param in fnmatch.filter(query_params, kq):
params[param] = request.params[param]
params = werkzeug.urls.url_encode(params)
if params:
location += '?%s' % params
return path
return location
def urlplus(url, params):
if not params:
@ -103,32 +109,28 @@ class website(osv.osv):
public_user = None
def get_public_user(self):
def get_public_user(self, cr, uid, context=None):
if not self.public_user:
ref = request.registry['ir.model.data'].get_object_reference(request.cr, openerp.SUPERUSER_ID, 'website', 'public_user')
self.public_user = request.registry[ref[0]].browse(request.cr, openerp.SUPERUSER_ID, ref[1])
uid = openerp.SUPERUSER_ID
ref = self.pool['ir.model.data'].get_object_reference(cr, uid, 'website', 'public_user')
self.public_user = self.pool[ref[0]].browse(cr, uid, ref[1])
return self.public_user
def get_lang(self):
website = request.registry['website'].get_current()
if hasattr(request, 'route_lang'):
lang = request.route_lang
else:
lang = request.params.get('lang', None) or request.httprequest.cookies.get('lang', None)
if lang not in [lg.code for lg in website.language_ids]:
lang = website.default_lang_id.code
return lang
def preprocess_request(self, cr, uid, ids, *args, **kwargs):
def preprocess_request(self, cr, uid, ids, request, context=None):
def redirect(url):
return werkzeug.utils.redirect(url_for(url))
request.redirect = redirect
is_public_user = request.uid == self.get_public_user().id
lang = self.get_lang()
is_public_user = request.uid == self.get_public_user(cr, uid, context).id
# Select current language
if hasattr(request, 'route_lang'):
lang = request.route_lang
else:
lang = request.params.get('lang', None) or request.httprequest.cookies.get('lang', None)
if lang not in [lg.code for lg in request.website.language_ids]:
lang = request.website.default_lang_id.code
is_master_lang = lang == request.website.default_lang_id.code
request.context.update({
'lang': lang,
@ -141,16 +143,15 @@ class website(osv.osv):
'translatable': not is_public_user and not is_master_lang and request.multilang,
})
def get_current(self):
# WIP, currently hard coded
return self.browse(request.cr, request.uid, 1)
def render(self, cr, uid, ids, template, values=None, context=None):
view = self.pool.get("ir.ui.view")
IMD = self.pool.get("ir.model.data")
user = self.pool.get("res.users")
def render(self, cr, uid, ids, template, values=None):
view = request.registry.get("ir.ui.view")
IMD = request.registry.get("ir.model.data")
user = request.registry.get("res.users")
if not context:
context = {}
qweb_context = request.context.copy()
qweb_context = context.copy()
if values:
qweb_context.update(values)
@ -164,7 +165,6 @@ class website(osv.osv):
user_id=user.browse(cr, uid, uid),
)
context = request.context.copy()
context.update(
inherit_branding=qweb_context.setdefault('editable', False),
)
@ -179,12 +179,11 @@ class website(osv.osv):
try:
view_ref = IMD.get_object_reference(cr, uid, module, xmlid)
except ValueError:
logger.error("Website Rendering Error.\n\n%s" % traceback.format_exc())
return self.render(cr, uid, ids, 'website.404', qweb_context)
return self.error(cr, uid, 404, qweb_context, context=context)
if 'main_object' not in qweb_context:
try:
main_object = request.registry[view_ref[0]].browse(cr, uid, view_ref[1])
main_object = self.pool[view_ref[0]].browse(cr, uid, view_ref[1])
qweb_context['main_object'] = main_object
except Exception:
pass
@ -197,26 +196,25 @@ class website(osv.osv):
logger.error(err)
qweb_context['error'] = err[1]
logger.warn("Website Rendering Error.\n\n%s" % traceback.format_exc())
return self.render(cr, uid, ids, 'website.401', qweb_context)
except (QWebException,), err:
return self.error(cr, uid, 401, qweb_context, context=context)
except Exception, e:
qweb_context['template'] = getattr(e, 'qweb_template', '')
node = getattr(e, 'qweb_node', None)
qweb_context['node'] = node and node.toxml()
qweb_context['expr'] = getattr(e, 'qweb_eval', '')
qweb_context['traceback'] = traceback.format_exc()
qweb_context['template'] = err.template
qweb_context['message'] = err.message
qweb_context['node'] = err.node and err.node.toxml()
logger.error("Website Rendering Error.\n%(message)s\n%(node)s\n\n%(traceback)s" % qweb_context)
return view.render(
cr, uid,
'website.500' if qweb_context['editable'] else 'website.404',
qweb_context, context=context)
except Exception:
logger.exception("Website Rendering Error.")
qweb_context['traceback'] = traceback.format_exc()
return view.render(
cr, uid,
'website.500' if qweb_context['editable'] else 'website.404',
qweb_context, context=context)
logger.exception("Website Rendering Error.\n%(template)s\n%(expr)s\n%(node)s" % qweb_context)
return self.error(cr, uid, 500 if qweb_context['editable'] else 404,
qweb_context, context=context)
def pager(self, cr, uid, ids, url, total, page=1, step=30, scope=5, url_args=None):
def error(self, cr, uid, code, qweb_context, context=None):
View = request.registry['ir.ui.view']
return werkzeug.wrappers.Response(
View.render(cr, uid, 'website.%d' % code, qweb_context),
status=code,
content_type='text/html;charset=utf-8')
def pager(self, cr, uid, ids, url, total, page=1, step=30, scope=5, url_args=None, context=None):
# Compute Pager
page_count = int(math.ceil(float(total) / step))
@ -264,6 +262,64 @@ class website(osv.osv):
]
}
def rule_is_enumerable(self, rule):
""" Checks that it is possible to generate sensible GET queries for
a given rule (if the endpoint matches its own requirements)
:type rule: werkzeug.routing.Rule
:rtype: bool
"""
endpoint = rule.endpoint
methods = rule.methods or ['GET']
return (
'GET' in methods
and endpoint.exposed == 'http'
and endpoint.auth in ('none', 'public')
and getattr(endpoint, 'cms', False)
# ensure all converters on the rule are able to generate values for
# themselves
and all(hasattr(converter, 'generate')
for converter in rule._converters.itervalues())
) and self.endpoint_is_enumerable(rule)
def endpoint_is_enumerable(self, rule):
""" Verifies that it's possible to generate a valid url for the rule's
endpoint
:type rule: werkzeug.routing.Rule
:rtype: bool
"""
# apparently the decorator package makes getargspec work correctly
# on functions it decorates. That's not the case for
# @functools.wraps, so hack around to get the original function
# (and hope a single decorator was applied or we're hosed)
# FIXME: this is going to blow up if we want/need to use multiple @route (with various configurations) on a method
undecorated_func = rule.endpoint.func_closure[0].cell_contents
# If this is ever ported to py3, use signatures, it doesn't suck as much
spec = inspect.getargspec(undecorated_func)
# if *args or **kwargs, just bail the fuck out, only dragons can
# live there
if spec.varargs or spec.keywords:
return False
# remove all arguments with a default value from the list
defaults_count = len(spec.defaults or []) # spec.defaults can be None
# a[:-0] ~ a[:0] ~ [] -> replace defaults_count == 0 by None to get
# a[:None] ~ a
args = spec.args[:(-defaults_count or None)]
# params with defaults were removed, leftover allowed are:
# * self (technically should be first-parameter-of-instance-method but whatever)
# * any parameter mapping to a converter
return all(
(arg == 'self' or arg in rule._converters)
for arg in args)
def list_pages(self, cr, uid, ids, context=None):
""" Available pages in the website/CMS. This is mostly used for links
generation and can be overridden by modules setting up new HTML
@ -276,26 +332,33 @@ class website(osv.osv):
of the same.
:rtype: list({name: str, url: str})
"""
View = self.pool['ir.ui.view']
views = View.search_read(cr, uid, [['page', '=', True]],
fields=['name'], order='name', context=context)
xids = View.get_external_id(cr, uid, [view['id'] for view in views], context=context)
return [
{'name': view['name'], 'url': '/page/' + xids[view['id']]}
for view in views
if xids[view['id']]
]
router = request.httprequest.app.get_db_router(request.db)
def kanban(self, cr, uid, ids, model, domain, column, template, step=None, scope=None, orderby=None):
for rule in router.iter_rules():
endpoint = rule.endpoint
if not self.rule_is_enumerable(rule):
continue
generated = map(dict, itertools.product(*(
itertools.izip(itertools.repeat(name), converter.generate())
for name, converter in rule._converters.iteritems()
)))
for values in generated:
# rule.build returns (domain_part, rel_url)
url = rule.build(values, append_unknown=False)[1]
yield {'name': url, 'url': url }
def kanban(self, cr, uid, ids, model, domain, column, template, step=None, scope=None, orderby=None, context=None):
step = step and int(step) or 10
scope = scope and int(scope) or 5
orderby = orderby or "name"
get_args = dict(request.httprequest.args or {})
model_obj = request.registry[model]
model_obj = self.pool[model]
relation = model_obj._columns.get(column)._obj
relation_obj = request.registry[relation]
relation_obj = self.pool[relation]
get_args.setdefault('kanban', "")
kanban = get_args.pop('kanban')
@ -349,9 +412,9 @@ class website(osv.osv):
}
return request.website.render("website.kanban_contain", values)
def kanban_col(self, cr, uid, ids, model, domain, page, template, step, orderby):
def kanban_col(self, cr, uid, ids, model, domain, page, template, step, orderby, context=None):
html = ""
model_obj = request.registry[model]
model_obj = self.pool[model]
domain = safe_eval(domain)
step = int(step)
offset = (int(page)-1) * step
@ -361,6 +424,74 @@ class website(osv.osv):
html += request.website.render(template, {'object_id': object_id})
return html
def get_menu(self, cr, uid, ids, context=None):
return self.pool['website.menu'].get_menu(cr, uid, ids[0], context=context)
class website_menu(osv.osv):
_name = "website.menu"
_description = "Website Menu"
_columns = {
'name': fields.char('Menu', size=64, required=True, translate=True),
'url': fields.char('Url', required=True, translate=True),
'new_window': fields.boolean('New Window'),
'sequence': fields.integer('Sequence'),
# TODO: support multiwebsite once done for ir.ui.views
'website_id': fields.many2one('website', 'Website'),
'parent_id': fields.many2one('website.menu', 'Parent Menu', select=True, ondelete="cascade"),
'child_id': fields.one2many('website.menu', 'parent_id', string='Child Menus'),
'parent_left': fields.integer('Parent Left', select=True),
'parent_right': fields.integer('Parent Right', select=True),
}
_defaults = {
'url': '',
'sequence': 0,
}
_parent_store = True
_parent_order = 'sequence, name'
_order = "parent_left"
def get_menu(self, cr, uid, website_id, context=None):
root_domain = [('parent_id', '=', False)] # ('website_id', '=', website_id),
menu_ids = self.search(cr, uid, root_domain, context=context)
menu = self.browse(cr, uid, menu_ids, context=context)
return menu[0]
def get_tree(self, cr, uid, website_id, context=None):
def make_tree(node):
menu_node = dict(
id=node.id,
name=node.name,
url=node.url,
new_window=node.new_window,
sequence=node.sequence,
parent_id=node.parent_id.id,
children=[],
)
for child in node.child_id:
menu_node['children'].append(make_tree(child))
return menu_node
menu = self.get_menu(cr, uid, website_id, context=context)
return make_tree(menu)
def save(self, cr, uid, website_id, data, context=None):
def replace_id(old_id, new_id):
for menu in data['data']:
if menu['id'] == old_id:
menu['id'] = new_id
if menu['parent_id'] == old_id:
menu['parent_id'] = new_id
to_delete = data['to_delete']
if to_delete:
self.unlink(cr, uid, to_delete, context=context)
for menu in data['data']:
mid = menu['id']
if isinstance(mid, str):
new_id = self.create(cr, uid, {'name': menu['name']}, context=context)
replace_id(mid, new_id)
for menu in data['data']:
self.write(cr, uid, [menu['id']], menu, context=context)
return True
class ir_attachment(osv.osv):
_inherit = "ir.attachment"
def _website_url_get(self, cr, uid, ids, name, arg, context=None):
@ -401,6 +532,15 @@ class res_partner(osv.osv):
}
return urlplus('https://maps.google.be/maps' , params)
class res_company(osv.osv):
_inherit = "res.company"
def google_map_img(self, cr, uid, ids, zoom=8, width=298, height=298, context=None):
partner = self.browse(cr, openerp.SUPERUSER_ID, ids[0], context=context).parent_id
return partner and partner.google_map_img(zoom, width, height, context=context) or None
def google_map_link(self, cr, uid, ids, zoom=8, context=None):
partner = self.browse(cr, openerp.SUPERUSER_ID, ids[0], context=context).parent_id
return partner and partner.google_map_link(zoom, context=context) or None
class base_language_install(osv.osv):
_inherit = "base.language.install"
_columns = {

View File

@ -1,6 +1,7 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_website_public,website,website.model_website,base.group_public,1,0,0,0
access_website,website,website.model_website,base.group_user,1,0,0,0
access_website_menu,access_website_menu,model_website_menu,,1,0,0,0
access_website_converter_test,access_website_converter_test,model_website_converter_test,,1,1,1,1
access_website_converter_test_sub,access_website_converter_test_sub,model_website_converter_test_sub,,1,1,1,1
access_website_qweb,access_website_qweb,model_website_qweb,,0,0,0,0

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_website_public website website.model_website base.group_public 1 0 0 0
3 access_website website website.model_website base.group_user 1 0 0 0
4 access_website_menu access_website_menu model_website_menu 1 0 0 0
5 access_website_converter_test access_website_converter_test model_website_converter_test 1 1 1 1
6 access_website_converter_test_sub access_website_converter_test_sub model_website_converter_test_sub 1 1 1 1
7 access_website_qweb access_website_qweb model_website_qweb 0 0 0 0

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,429 @@
/*
* jQuery UI Nested Sortable
* v 1.3.5 / 21 jun 2012
* http://mjsarfatti.com/code/nestedSortable
*
* Depends on:
* jquery.ui.sortable.js 1.8+
*
* Copyright (c) 2010-2012 Manuele J Sarfatti
* Licensed under the MIT License
* http://www.opensource.org/licenses/mit-license.php
*/
(function($) {
$.widget("mjs.nestedSortable", $.extend({}, $.ui.sortable.prototype, {
options: {
tabSize: 20,
disableNesting: 'mjs-nestedSortable-no-nesting',
errorClass: 'mjs-nestedSortable-error',
doNotClear: false,
listType: 'ol',
maxLevels: 0,
protectRoot: false,
rootID: null,
rtl: false,
isAllowed: function(item, parent) { return true; }
},
_create: function() {
this.element.data('sortable', this.element.data('nestedSortable'));
if (!this.element.is(this.options.listType))
throw new Error('nestedSortable: Please check the listType option is set to your actual list type');
return $.ui.sortable.prototype._create.apply(this, arguments);
},
destroy: function() {
this.element
.removeData("nestedSortable")
.unbind(".nestedSortable");
return $.ui.sortable.prototype.destroy.apply(this, arguments);
},
_mouseDrag: function(event) {
//Compute the helpers position
this.position = this._generatePosition(event);
this.positionAbs = this._convertPositionTo("absolute");
if (!this.lastPositionAbs) {
this.lastPositionAbs = this.positionAbs;
}
var o = this.options;
//Do scrolling
if(this.options.scroll) {
var scrolled = false;
if(this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML') {
if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity)
this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity)
this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
} else {
if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
}
if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
$.ui.ddmanager.prepareOffsets(this, event);
}
//Regenerate the absolute position used for position checks
this.positionAbs = this._convertPositionTo("absolute");
// Find the top offset before rearrangement,
var previousTopOffset = this.placeholder.offset().top;
//Set the helper position
if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
//Rearrange
for (var i = this.items.length - 1; i >= 0; i--) {
//Cache variables and intersection, continue if no intersection
var item = this.items[i], itemElement = item.item[0], intersection = this._intersectsWithPointer(item);
if (!intersection) continue;
if(itemElement != this.currentItem[0] //cannot intersect with itself
&& this.placeholder[intersection == 1 ? "next" : "prev"]()[0] != itemElement //no useless actions that have been done before
&& !$.contains(this.placeholder[0], itemElement) //no action if the item moved is the parent of the item checked
&& (this.options.type == 'semi-dynamic' ? !$.contains(this.element[0], itemElement) : true)
//&& itemElement.parentNode == this.placeholder[0].parentNode // only rearrange items within the same container
) {
$(itemElement).mouseenter();
this.direction = intersection == 1 ? "down" : "up";
if (this.options.tolerance == "pointer" || this._intersectsWithSides(item)) {
$(itemElement).mouseleave();
this._rearrange(event, item);
} else {
break;
}
// Clear emtpy ul's/ol's
this._clearEmpty(itemElement);
this._trigger("change", event, this._uiHash());
break;
}
}
var parentItem = (this.placeholder[0].parentNode.parentNode &&
$(this.placeholder[0].parentNode.parentNode).closest('.ui-sortable').length)
? $(this.placeholder[0].parentNode.parentNode)
: null,
level = this._getLevel(this.placeholder),
childLevels = this._getChildLevels(this.helper);
// To find the previous sibling in the list, keep backtracking until we hit a valid list item.
var previousItem = this.placeholder[0].previousSibling ? $(this.placeholder[0].previousSibling) : null;
if (previousItem != null) {
while (previousItem[0].nodeName.toLowerCase() != 'li' || previousItem[0] == this.currentItem[0] || previousItem[0] == this.helper[0]) {
if (previousItem[0].previousSibling) {
previousItem = $(previousItem[0].previousSibling);
} else {
previousItem = null;
break;
}
}
}
// To find the next sibling in the list, keep stepping forward until we hit a valid list item.
var nextItem = this.placeholder[0].nextSibling ? $(this.placeholder[0].nextSibling) : null;
if (nextItem != null) {
while (nextItem[0].nodeName.toLowerCase() != 'li' || nextItem[0] == this.currentItem[0] || nextItem[0] == this.helper[0]) {
if (nextItem[0].nextSibling) {
nextItem = $(nextItem[0].nextSibling);
} else {
nextItem = null;
break;
}
}
}
var newList = document.createElement(o.listType);
this.beyondMaxLevels = 0;
// If the item is moved to the left, send it to its parent's level unless there are siblings below it.
if (parentItem != null && nextItem == null &&
(o.rtl && (this.positionAbs.left + this.helper.outerWidth() > parentItem.offset().left + parentItem.outerWidth()) ||
!o.rtl && (this.positionAbs.left < parentItem.offset().left))) {
parentItem.after(this.placeholder[0]);
this._clearEmpty(parentItem[0]);
this._trigger("change", event, this._uiHash());
}
// If the item is below a sibling and is moved to the right, make it a child of that sibling.
else if (previousItem != null &&
(o.rtl && (this.positionAbs.left + this.helper.outerWidth() < previousItem.offset().left + previousItem.outerWidth() - o.tabSize) ||
!o.rtl && (this.positionAbs.left > previousItem.offset().left + o.tabSize))) {
this._isAllowed(previousItem, level, level+childLevels+1);
if (!previousItem.children(o.listType).length) {
previousItem[0].appendChild(newList);
}
// If this item is being moved from the top, add it to the top of the list.
if (previousTopOffset && (previousTopOffset <= previousItem.offset().top)) {
previousItem.children(o.listType).prepend(this.placeholder);
}
// Otherwise, add it to the bottom of the list.
else {
previousItem.children(o.listType)[0].appendChild(this.placeholder[0]);
}
this._trigger("change", event, this._uiHash());
}
else {
this._isAllowed(parentItem, level, level+childLevels);
}
//Post events to containers
this._contactContainers(event);
//Interconnect with droppables
if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
//Call callbacks
this._trigger('sort', event, this._uiHash());
this.lastPositionAbs = this.positionAbs;
return false;
},
_mouseStop: function(event, noPropagation) {
// If the item is in a position not allowed, send it back
if (this.beyondMaxLevels) {
this.placeholder.removeClass(this.options.errorClass);
if (this.domPosition.prev) {
$(this.domPosition.prev).after(this.placeholder);
} else {
$(this.domPosition.parent).prepend(this.placeholder);
}
this._trigger("revert", event, this._uiHash());
}
// Clean last empty ul/ol
for (var i = this.items.length - 1; i >= 0; i--) {
var item = this.items[i].item[0];
this._clearEmpty(item);
}
$.ui.sortable.prototype._mouseStop.apply(this, arguments);
},
serialize: function(options) {
var o = $.extend({}, this.options, options),
items = this._getItemsAsjQuery(o && o.connected),
str = [];
$(items).each(function() {
var res = ($(o.item || this).attr(o.attribute || 'id') || '')
.match(o.expression || (/(.+)[-=_](.+)/)),
pid = ($(o.item || this).parent(o.listType)
.parent(o.items)
.attr(o.attribute || 'id') || '')
.match(o.expression || (/(.+)[-=_](.+)/));
if (res) {
str.push(((o.key || res[1]) + '[' + (o.key && o.expression ? res[1] : res[2]) + ']')
+ '='
+ (pid ? (o.key && o.expression ? pid[1] : pid[2]) : o.rootID));
}
});
if(!str.length && o.key) {
str.push(o.key + '=');
}
return str.join('&');
},
toHierarchy: function(options) {
var o = $.extend({}, this.options, options),
sDepth = o.startDepthCount || 0,
ret = [];
$(this.element).children(o.items).each(function () {
var level = _recursiveItems(this);
ret.push(level);
});
return ret;
function _recursiveItems(item) {
var id = ($(item).attr(o.attribute || 'id') || '').match(o.expression || (/(.+)[-=_](.+)/));
if (id) {
var currentItem = {"id" : id[2]};
if ($(item).children(o.listType).children(o.items).length > 0) {
currentItem.children = [];
$(item).children(o.listType).children(o.items).each(function() {
var level = _recursiveItems(this);
currentItem.children.push(level);
});
}
return currentItem;
}
}
},
toArray: function(options) {
var o = $.extend({}, this.options, options),
sDepth = o.startDepthCount || 0,
ret = [],
left = 2;
ret.push({
"item_id": o.rootID,
"parent_id": 'none',
"depth": sDepth,
"left": '1',
"right": ($(o.items, this.element).length + 1) * 2
});
$(this.element).children(o.items).each(function () {
left = _recursiveArray(this, sDepth + 1, left);
});
ret = ret.sort(function(a,b){ return (a.left - b.left); });
return ret;
function _recursiveArray(item, depth, left) {
var right = left + 1,
id,
pid;
if ($(item).children(o.listType).children(o.items).length > 0) {
depth ++;
$(item).children(o.listType).children(o.items).each(function () {
right = _recursiveArray($(this), depth, right);
});
depth --;
}
id = ($(item).attr(o.attribute || 'id')).match(o.expression || (/(.+)[-=_](.+)/));
if (depth === sDepth + 1) {
pid = o.rootID;
} else {
var parentItem = ($(item).parent(o.listType)
.parent(o.items)
.attr(o.attribute || 'id'))
.match(o.expression || (/(.+)[-=_](.+)/));
pid = parentItem[2];
}
if (id) {
ret.push({"item_id": id[2], "parent_id": pid, "depth": depth, "left": left, "right": right});
}
left = right + 1;
return left;
}
},
_clearEmpty: function(item) {
var emptyList = $(item).children(this.options.listType);
if (emptyList.length && !emptyList.children().length && !this.options.doNotClear) {
emptyList.remove();
}
},
_getLevel: function(item) {
var level = 1;
if (this.options.listType) {
var list = item.closest(this.options.listType);
while (list && list.length > 0 &&
!list.is('.ui-sortable')) {
level++;
list = list.parent().closest(this.options.listType);
}
}
return level;
},
_getChildLevels: function(parent, depth) {
var self = this,
o = this.options,
result = 0;
depth = depth || 0;
$(parent).children(o.listType).children(o.items).each(function (index, child) {
result = Math.max(self._getChildLevels(child, depth + 1), result);
});
return depth ? result + 1 : result;
},
_isAllowed: function(parentItem, level, levels) {
var o = this.options,
isRoot = $(this.domPosition.parent).hasClass('ui-sortable') ? true : false,
maxLevels = this.placeholder.closest('.ui-sortable').nestedSortable('option', 'maxLevels'); // this takes into account the maxLevels set to the recipient list
// Is the root protected?
// Are we trying to nest under a no-nest?
// Are we nesting too deep?
if (!o.isAllowed(this.currentItem, parentItem) ||
parentItem && parentItem.hasClass(o.disableNesting) ||
o.protectRoot && (parentItem == null && !isRoot || isRoot && level > 1)) {
this.placeholder.addClass(o.errorClass);
if (maxLevels < levels && maxLevels != 0) {
this.beyondMaxLevels = levels - maxLevels;
} else {
this.beyondMaxLevels = 1;
}
} else {
if (maxLevels < levels && maxLevels != 0) {
this.placeholder.addClass(o.errorClass);
this.beyondMaxLevels = levels - maxLevels;
} else {
this.placeholder.removeClass(o.errorClass);
this.beyondMaxLevels = 0;
}
}
}
}));
$.mjs.nestedSortable.prototype.options = $.extend({}, $.ui.sortable.prototype.options, $.mjs.nestedSortable.prototype.options);
})(jQuery);

View File

@ -1,20 +0,0 @@
Copyright (c) 2013 Mark Dalgleish
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.

View File

@ -1,308 +0,0 @@
[![Build Status](https://secure.travis-ci.org/markdalgleish/stellar.js.png)](http://travis-ci.org/markdalgleish/stellar.js)
# Stellar.js
### Parallax scrolling made easy
Full guide and demonstrations available at the [official Stellar.js project page](http://markdalgleish.com/projects/stellar.js/).
## Download
Get the [development](https://raw.github.com/markdalgleish/stellar.js/master/jquery.stellar.js) or [production](https://raw.github.com/markdalgleish/stellar.js/master/jquery.stellar.min.js) version, or use a [package manager](https://github.com/markdalgleish/stellar.js#package-managers).
## Getting Started
Stellar.js is a jQuery plugin that provides parallax scrolling effects to any scrolling element. The first step is to run `.stellar()` against the element:
``` js
// For example:
$(window).stellar();
// or:
$('#main').stellar();
```
If you're running Stellar.js on 'window', you can use the shorthand:
``` js
$.stellar();
```
This will look for any parallax backgrounds or elements within the specified element and reposition them when the element scrolls.
## Mobile Support
Support in Mobile WebKit browsers requires a touch scrolling library, and a slightly tweaked configuration. For a full walkthrough on how to implement this correctly, read my blog post ["Mobile Parallax with Stellar.js"](http://markdalgleish.com/2012/10/mobile-parallax-with-stellar-js).
Please note that parallax backgrounds are not recommended in Mobile WebKit due to performance constraints. Instead, use parallax elements with static backgrounds.
## Parallax Elements
If you want elements to scroll at a different speed, add the following attribute to any element with a CSS position of absolute, relative or fixed:
``` html
<div data-stellar-ratio="2">
```
The ratio is relative to the natural scroll speed, so a ratio of 0.5 would cause the element to scroll at half-speed, a ratio of 1 would have no effect, and a ratio of 2 would cause the element to scroll at twice the speed. If a ratio lower than 1 is causing the element to appear jittery, try setting its CSS position to fixed.
In order for Stellar.js to perform its calculations correctly, all parallax elements must have their dimensions specified in pixels for the axis/axes being used for parallax effects. For example, all parallax elements for a vertical site must have a pixel height specified. If your design prohibits the use of pixels, try using the ['responsive' option](#configuring-everything).
## Parallax Backgrounds
If you want an element's background image to reposition on scroll, simply add the following attribute:
``` html
<div data-stellar-background-ratio="0.5">
```
As with parallax elements, the ratio is relative to the natural scroll speed. For ratios lower than 1, to avoid jittery scroll performance, set the element's CSS 'background-attachment' to fixed.
## Configuring Offsets
Stellar.js' most powerful feature is the way it aligns elements.
All elements will return to their original positioning when their offset parent meets the edge of the screen—plus or minus your own optional offset. This allows you to create intricate parallax patterns very easily.
Confused? [See how offsets are used on the Stellar.js home page.](http://markdalgleish.com/projects/stellar.js/#show-offsets)
To modify the offsets for all elements at once, pass in the options:
``` js
$.stellar({
horizontalOffset: 40,
verticalOffset: 150
});
```
You can also modify the offsets on a per-element basis using the following data attributes:
``` html
<div data-stellar-ratio="2"
data-stellar-horizontal-offset="40"
data-stellar-vertical-offset="150">
```
## Configuring Offset Parents
By default, offsets are relative to the element's offset parent. This mirrors the way an absolutely positioned element behaves when nested inside an element with a relative position.
As with regular CSS, the closest parent element with a position of relative or absolute is the offset parent.
To override this and force the offset parent to be another element higher up the DOM, use the following data attribute:
``` html
<div data-stellar-offset-parent="true">
```
The offset parent can also have its own offsets:
``` html
<div data-stellar-offset-parent="true"
data-stellar-horizontal-offset="40"
data-stellar-vertical-offset="150">
```
Similar to CSS, the rules take precedence from element, to offset parent, to JavaScript options.
Confused? [See how offset parents are used on the Stellar.js home page.](http://markdalgleish.com/projects/stellar.js/#show-offset-parents)
Still confused? [See what it looks like with its default offset parents.](http://markdalgleish.com/projects/stellar.js/#show-offset-parents-default) Notice how the alignment happens on a per-letter basis? That's because each letter's containing div is its default offset parent.
By specifying the h2 element as the offset parent, we can ensure that the alignment of all the stars in a heading is based on the h2 and not the div further down the DOM tree.
## Configuring Scroll Positioning
You can define what it means for an element to 'scroll'. Whether it's the element's scroll position that's changing, its margins or its CSS3 'transform' position, you can define it using the 'scrollProperty' option:
``` js
$('#gallery').stellar({
scrollProperty: 'transform'
});
```
This option is what allows you to run [Stellar.js on iOS](http://markdalgleish.com/projects/stellar.js/demos/ios.html).
You can even define how the elements are repositioned, whether it's through standard top and left properties or using CSS3 transforms:
``` js
$('#gallery').stellar({
positionProperty: 'transform'
});
```
Don't have the level of control you need? Write a plugin!
Otherwise, you're ready to get started!
## Configuring Everything
Below you will find a complete list of options and matching default values:
``` js
$.stellar({
// Set scrolling to be in either one or both directions
horizontalScrolling: true,
verticalScrolling: true,
// Set the global alignment offsets
horizontalOffset: 0,
verticalOffset: 0,
// Refreshes parallax content on window load and resize
responsive: false,
// Select which property is used to calculate scroll.
// Choose 'scroll', 'position', 'margin' or 'transform',
// or write your own 'scrollProperty' plugin.
scrollProperty: 'scroll',
// Select which property is used to position elements.
// Choose between 'position' or 'transform',
// or write your own 'positionProperty' plugin.
positionProperty: 'position',
// Enable or disable the two types of parallax
parallaxBackgrounds: true,
parallaxElements: true,
// Hide parallax elements that move outside the viewport
hideDistantElements: true,
// Customise how elements are shown and hidden
hideElement: function($elem) { $elem.hide(); },
showElement: function($elem) { $elem.show(); }
});
```
## Writing a Scroll Property Plugin
Out of the box, Stellar.js supports the following scroll properties:
'scroll', 'position', 'margin' and 'transform'.
If your method for creating a scrolling interface isn't covered by one of these, you can write your own. For example, if 'margin' didn't exist yet you could write it like so:
``` js
$.stellar.scrollProperty.margin = {
getLeft: function($element) {
return parseInt($element.css('margin-left'), 10) * -1;
},
getTop: function($element) {
return parseInt($element.css('margin-top'), 10) * -1;
}
}
```
Now, you can specify this scroll property in Stellar.js' configuration.
``` js
$.stellar({
scrollProperty: 'margin'
});
```
## Writing a Position Property Plugin
Stellar.js has two methods for positioning elements built in: 'position' for modifying its top and left properties, and 'transform' for using CSS3 transforms.
If you need more control over how elements are positioned, you can write your own setter functions. For example, if 'position' didn't exist yet, it could be written as a plugin like this:
``` js
$.stellar.positionProperty.position = {
setTop: function($element, newTop, originalTop) {
$element.css('top', newTop);
},
setLeft: function($element, newLeft, originalLeft) {
$element.css('left', newLeft);
}
}
```
Now, you can specify this position property in Stellar.js' configuration.
``` js
$.stellar({
positionProperty: 'position'
});
```
If, for technical reasons, you need to set both properties at once, you can define a single 'setPosition' function:
``` js
$.stellar.positionProperty.foobar = {
setPosition: function($element, newLeft, originalLeft, newTop, originalTop) {
$element.css('transform', 'translate3d(' +
(newLeft - originalLeft) + 'px, ' +
(newTop - originalTop) + 'px, ' +
'0)');
}
}
$.stellar({
positionProperty: 'foobar'
});
```
## Package Managers
Stellar.js can be installed with [Bower](http://twitter.github.com/bower/):
``` bash
$ bower install jquery.stellar
```
## Sites Using Stellar.js
* [National Geographic - Alien Deep Interactive](http://channel.nationalgeographic.com/channel/alien-deep/interactives/alien-deep-interactive)
* [François Hollande](http://www.parti-socialiste.fr/latimelineduchangement)
* [Brabus Private Aviation](http://www.brabus-aviation.com/)
* [Mary and Frankie's Wedding](http://www.maryandfrankiewedding.com/)
* [IT Support London](http://www.itsupportlondon.com)
* [Ashford University](http://bright.ashford.edu)
* [Clif Adventures](http://www.clifbar.com/adventures)
* [Mindster](http://www.mindster.org)
* [WS Interactive](http://www.ws-interactive.fr/methode)
* [Moire Mag - Untitled](http://www.moiremag.net/untitled)
* [Carnival of Courage](http://www.carnivalofcourage.com.au)
* [Ian Poulter](http://www.ianpoulter.com)
* [360 Strategy Group](http://360strategygroup.com)
* [Code, Love and Boards](http://codeloveandboards.com/)
I'm sure there are heaps more. [Let me know if you'd like me to feature your site here.](http://twitter.com/markdalgleish)
## How to Build
Stellar.js uses [Node.js](nodejs.org), [Grunt](http://gruntjs.com) and [PhantomJS](http://phantomjs.org/).
Once you've got Node and PhantomJS set up, install the dependencies:
`$ npm install`
To lint, test and minify the project, simply run the following command:
`$ grunt`
Each of the build steps are also available individually.
`$ grunt test` to test the code using QUnit and PhantomJS:
`$ grunt lint` to validate the code using JSHint.
`$ grunt watch` to continuously lint and test the code while developing.
## Contributing to Stellar.js
Ensure that you successfully test and build the project with `$ grunt` before committing.
Make sure that all plugin changes are made in `src/jquery.stellar.js` (`/jquery.stellar.js` and `/jquery.stellar.min.js` are generated by Grunt).
If you want to contribute in a way that changes the API, please file an issue before submitting a pull request so we can discuss how to appropriately integrate your ideas.
## Questions?
Contact me on GitHub or Twitter: [@markdalgleish](http://twitter.com/markdalgleish)
## License
Copyright 2013, Mark Dalgleish
This content is released under the MIT license
http://markdalgleish.mit-license.org

View File

@ -1,8 +0,0 @@
{
"name": "jquery.stellar",
"version": "0.6.2",
"main": ["./jquery.stellar.js"],
"dependencies": {
"jquery": ">=1.4.3"
}
}

View File

@ -1,71 +0,0 @@
/*global module:false*/
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: '<json:package.json>',
meta: {
banner: '/*!\n' +
' * <%= pkg.title || pkg.name %> v<%= pkg.version %>\n' +
' * <%= pkg.homepage %>\n' +
' * \n' +
' * Copyright <%= grunt.template.today("yyyy") %>, <%= pkg.author.name %>\n' +
' * This content is released under the <%= _.pluck(pkg.licenses, "type").join(", ") %> license<%= pkg.licenses.length === 1 ? "" : "s" %>\n' +
' * <%= _.pluck(pkg.licenses, "url").join(", ") %>\n' +
' */',
microbanner: '/*! <%= pkg.title || pkg.name %> v<%= pkg.version %> | Copyright <%= grunt.template.today("yyyy") %>, <%= pkg.author.name %> | <%= pkg.homepage %> | <%= _.pluck(pkg.licenses, "url").join(", ") %> */'
},
lint: {
files: ['grunt.js', 'test/**/*.js', 'src/**/*.js']
},
server: {
port: 8573
},
qunit: {
urls: ['1.4.3', '1.10.1', '2.0.2'].map(function(version) {
return 'http://localhost:<%= server.port %>/test/jquery.stellar.html?jquery=' + version;
})
},
concat: {
dist: {
src: ['<banner:meta.banner>', '<file_strip_banner:src/<%= pkg.name %>.js>'],
dest: '<%= pkg.name %>.js'
}
},
min: {
dist: {
src: ['<banner:meta.microbanner>', '<config:concat.dist.dest>'],
dest: '<%= pkg.name %>.min.js'
}
},
watch: {
files: '<config:lint.files>',
tasks: 'server lint qunit'
},
jshint: {
options: {
evil: true,
curly: false,
eqeqeq: true,
immed: true,
latedef: true,
newcap: true,
noarg: true,
sub: true,
undef: true,
boss: true,
browser: true
},
globals: {
jQuery: true
}
},
uglify: {}
});
// Default task.
grunt.registerTask('default', 'server lint qunit concat min');
grunt.registerTask('test', 'server lint qunit');
};

View File

@ -1,660 +0,0 @@
/*!
* Stellar.js v0.6.2
* http://markdalgleish.com/projects/stellar.js
*
* Copyright 2013, Mark Dalgleish
* This content is released under the MIT license
* http://markdalgleish.mit-license.org
*/
;(function($, window, document, undefined) {
var pluginName = 'stellar',
defaults = {
scrollProperty: 'scroll',
positionProperty: 'position',
horizontalScrolling: true,
verticalScrolling: true,
horizontalOffset: 0,
verticalOffset: 0,
responsive: false,
parallaxBackgrounds: true,
parallaxElements: true,
hideDistantElements: true,
hideElement: function($elem) { $elem.hide(); },
showElement: function($elem) { $elem.show(); }
},
scrollProperty = {
scroll: {
getLeft: function($elem) { return $elem.scrollLeft(); },
setLeft: function($elem, val) { $elem.scrollLeft(val); },
getTop: function($elem) { return $elem.scrollTop(); },
setTop: function($elem, val) { $elem.scrollTop(val); }
},
position: {
getLeft: function($elem) { return parseInt($elem.css('left'), 10) * -1; },
getTop: function($elem) { return parseInt($elem.css('top'), 10) * -1; }
},
margin: {
getLeft: function($elem) { return parseInt($elem.css('margin-left'), 10) * -1; },
getTop: function($elem) { return parseInt($elem.css('margin-top'), 10) * -1; }
},
transform: {
getLeft: function($elem) {
var computedTransform = getComputedStyle($elem[0])[prefixedTransform];
return (computedTransform !== 'none' ? parseInt(computedTransform.match(/(-?[0-9]+)/g)[4], 10) * -1 : 0);
},
getTop: function($elem) {
var computedTransform = getComputedStyle($elem[0])[prefixedTransform];
return (computedTransform !== 'none' ? parseInt(computedTransform.match(/(-?[0-9]+)/g)[5], 10) * -1 : 0);
}
}
},
positionProperty = {
position: {
setLeft: function($elem, left) { $elem.css('left', left); },
setTop: function($elem, top) { $elem.css('top', top); }
},
transform: {
setPosition: function($elem, left, startingLeft, top, startingTop) {
$elem[0].style[prefixedTransform] = 'translate3d(' + (left - startingLeft) + 'px, ' + (top - startingTop) + 'px, 0)';
}
}
},
// Returns a function which adds a vendor prefix to any CSS property name
vendorPrefix = (function() {
var prefixes = /^(Moz|Webkit|Khtml|O|ms|Icab)(?=[A-Z])/,
style = $('script')[0].style,
prefix = '',
prop;
for (prop in style) {
if (prefixes.test(prop)) {
prefix = prop.match(prefixes)[0];
break;
}
}
if ('WebkitOpacity' in style) { prefix = 'Webkit'; }
if ('KhtmlOpacity' in style) { prefix = 'Khtml'; }
return function(property) {
return prefix + (prefix.length > 0 ? property.charAt(0).toUpperCase() + property.slice(1) : property);
};
}()),
prefixedTransform = vendorPrefix('transform'),
supportsBackgroundPositionXY = $('<div />', { style: 'background:#fff' }).css('background-position-x') !== undefined,
setBackgroundPosition = (supportsBackgroundPositionXY ?
function($elem, x, y) {
$elem.css({
'background-position-x': x,
'background-position-y': y
});
} :
function($elem, x, y) {
$elem.css('background-position', x + ' ' + y);
}
),
getBackgroundPosition = (supportsBackgroundPositionXY ?
function($elem) {
return [
$elem.css('background-position-x'),
$elem.css('background-position-y')
];
} :
function($elem) {
return $elem.css('background-position').split(' ');
}
),
requestAnimFrame = (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback) {
setTimeout(callback, 1000 / 60);
}
);
function Plugin(element, options) {
this.element = element;
this.options = $.extend({}, defaults, options);
this._defaults = defaults;
this._name = pluginName;
this.init();
}
Plugin.prototype = {
init: function() {
this.options.name = pluginName + '_' + Math.floor(Math.random() * 1e9);
this._defineElements();
this._defineGetters();
this._defineSetters();
this._handleWindowLoadAndResize();
this._detectViewport();
this.refresh({ firstLoad: true });
if (this.options.scrollProperty === 'scroll') {
this._handleScrollEvent();
} else {
this._startAnimationLoop();
}
},
_defineElements: function() {
if (this.element === document.body) this.element = window;
this.$scrollElement = $(this.element);
this.$element = (this.element === window ? $('body') : this.$scrollElement);
this.$viewportElement = (this.options.viewportElement !== undefined ? $(this.options.viewportElement) : (this.$scrollElement[0] === window || this.options.scrollProperty === 'scroll' ? this.$scrollElement : this.$scrollElement.parent()) );
},
_defineGetters: function() {
var self = this,
scrollPropertyAdapter = scrollProperty[self.options.scrollProperty];
this._getScrollLeft = function() {
return scrollPropertyAdapter.getLeft(self.$scrollElement);
};
this._getScrollTop = function() {
return scrollPropertyAdapter.getTop(self.$scrollElement);
};
},
_defineSetters: function() {
var self = this,
scrollPropertyAdapter = scrollProperty[self.options.scrollProperty],
positionPropertyAdapter = positionProperty[self.options.positionProperty],
setScrollLeft = scrollPropertyAdapter.setLeft,
setScrollTop = scrollPropertyAdapter.setTop;
this._setScrollLeft = (typeof setScrollLeft === 'function' ? function(val) {
setScrollLeft(self.$scrollElement, val);
} : $.noop);
this._setScrollTop = (typeof setScrollTop === 'function' ? function(val) {
setScrollTop(self.$scrollElement, val);
} : $.noop);
this._setPosition = positionPropertyAdapter.setPosition ||
function($elem, left, startingLeft, top, startingTop) {
if (self.options.horizontalScrolling) {
positionPropertyAdapter.setLeft($elem, left, startingLeft);
}
if (self.options.verticalScrolling) {
positionPropertyAdapter.setTop($elem, top, startingTop);
}
};
},
_handleWindowLoadAndResize: function() {
var self = this,
$window = $(window);
if (self.options.responsive) {
$window.bind('load.' + this.name, function() {
self.refresh();
});
}
$window.bind('resize.' + this.name, function() {
self._detectViewport();
if (self.options.responsive) {
self.refresh();
}
});
},
refresh: function(options) {
var self = this,
oldLeft = self._getScrollLeft(),
oldTop = self._getScrollTop();
if (!options || !options.firstLoad) {
this._reset();
}
this._setScrollLeft(0);
this._setScrollTop(0);
this._setOffsets();
this._findParticles();
this._findBackgrounds();
// Fix for WebKit background rendering bug
if (options && options.firstLoad && /WebKit/.test(navigator.userAgent)) {
$(window).load(function() {
var oldLeft = self._getScrollLeft(),
oldTop = self._getScrollTop();
self._setScrollLeft(oldLeft + 1);
self._setScrollTop(oldTop + 1);
self._setScrollLeft(oldLeft);
self._setScrollTop(oldTop);
});
}
this._setScrollLeft(oldLeft);
this._setScrollTop(oldTop);
},
_detectViewport: function() {
var viewportOffsets = this.$viewportElement.offset(),
hasOffsets = viewportOffsets !== null && viewportOffsets !== undefined;
this.viewportWidth = this.$viewportElement.width();
this.viewportHeight = this.$viewportElement.height();
this.viewportOffsetTop = (hasOffsets ? viewportOffsets.top : 0);
this.viewportOffsetLeft = (hasOffsets ? viewportOffsets.left : 0);
},
_findParticles: function() {
var self = this,
scrollLeft = this._getScrollLeft(),
scrollTop = this._getScrollTop();
if (this.particles !== undefined) {
for (var i = this.particles.length - 1; i >= 0; i--) {
this.particles[i].$element.data('stellar-elementIsActive', undefined);
}
}
this.particles = [];
if (!this.options.parallaxElements) return;
this.$element.find('[data-stellar-ratio]').each(function(i) {
var $this = $(this),
horizontalOffset,
verticalOffset,
positionLeft,
positionTop,
marginLeft,
marginTop,
$offsetParent,
offsetLeft,
offsetTop,
parentOffsetLeft = 0,
parentOffsetTop = 0,
tempParentOffsetLeft = 0,
tempParentOffsetTop = 0;
// Ensure this element isn't already part of another scrolling element
if (!$this.data('stellar-elementIsActive')) {
$this.data('stellar-elementIsActive', this);
} else if ($this.data('stellar-elementIsActive') !== this) {
return;
}
self.options.showElement($this);
// Save/restore the original top and left CSS values in case we refresh the particles or destroy the instance
if (!$this.data('stellar-startingLeft')) {
$this.data('stellar-startingLeft', $this.css('left'));
$this.data('stellar-startingTop', $this.css('top'));
} else {
$this.css('left', $this.data('stellar-startingLeft'));
$this.css('top', $this.data('stellar-startingTop'));
}
positionLeft = $this.position().left;
positionTop = $this.position().top;
// Catch-all for margin top/left properties (these evaluate to 'auto' in IE7 and IE8)
marginLeft = ($this.css('margin-left') === 'auto') ? 0 : parseInt($this.css('margin-left'), 10);
marginTop = ($this.css('margin-top') === 'auto') ? 0 : parseInt($this.css('margin-top'), 10);
offsetLeft = $this.offset().left - marginLeft;
offsetTop = $this.offset().top - marginTop;
// Calculate the offset parent
$this.parents().each(function() {
var $this = $(this);
if ($this.data('stellar-offset-parent') === true) {
parentOffsetLeft = tempParentOffsetLeft;
parentOffsetTop = tempParentOffsetTop;
$offsetParent = $this;
return false;
} else {
tempParentOffsetLeft += $this.position().left;
tempParentOffsetTop += $this.position().top;
}
});
// Detect the offsets
horizontalOffset = ($this.data('stellar-horizontal-offset') !== undefined ? $this.data('stellar-horizontal-offset') : ($offsetParent !== undefined && $offsetParent.data('stellar-horizontal-offset') !== undefined ? $offsetParent.data('stellar-horizontal-offset') : self.horizontalOffset));
verticalOffset = ($this.data('stellar-vertical-offset') !== undefined ? $this.data('stellar-vertical-offset') : ($offsetParent !== undefined && $offsetParent.data('stellar-vertical-offset') !== undefined ? $offsetParent.data('stellar-vertical-offset') : self.verticalOffset));
// Add our object to the particles collection
self.particles.push({
$element: $this,
$offsetParent: $offsetParent,
isFixed: $this.css('position') === 'fixed',
horizontalOffset: horizontalOffset,
verticalOffset: verticalOffset,
startingPositionLeft: positionLeft,
startingPositionTop: positionTop,
startingOffsetLeft: offsetLeft,
startingOffsetTop: offsetTop,
parentOffsetLeft: parentOffsetLeft,
parentOffsetTop: parentOffsetTop,
stellarRatio: ($this.data('stellar-ratio') !== undefined ? $this.data('stellar-ratio') : 1),
width: $this.outerWidth(true),
height: $this.outerHeight(true),
isHidden: false
});
});
},
_findBackgrounds: function() {
var self = this,
scrollLeft = this._getScrollLeft(),
scrollTop = this._getScrollTop(),
$backgroundElements;
this.backgrounds = [];
if (!this.options.parallaxBackgrounds) return;
$backgroundElements = this.$element.find('[data-stellar-background-ratio]');
if (this.$element.data('stellar-background-ratio')) {
$backgroundElements = $backgroundElements.add(this.$element);
}
$backgroundElements.each(function() {
var $this = $(this),
backgroundPosition = getBackgroundPosition($this),
horizontalOffset,
verticalOffset,
positionLeft,
positionTop,
marginLeft,
marginTop,
offsetLeft,
offsetTop,
$offsetParent,
parentOffsetLeft = 0,
parentOffsetTop = 0,
tempParentOffsetLeft = 0,
tempParentOffsetTop = 0;
// Ensure this element isn't already part of another scrolling element
if (!$this.data('stellar-backgroundIsActive')) {
$this.data('stellar-backgroundIsActive', this);
} else if ($this.data('stellar-backgroundIsActive') !== this) {
return;
}
// Save/restore the original top and left CSS values in case we destroy the instance
if (!$this.data('stellar-backgroundStartingLeft')) {
$this.data('stellar-backgroundStartingLeft', backgroundPosition[0]);
$this.data('stellar-backgroundStartingTop', backgroundPosition[1]);
} else {
setBackgroundPosition($this, $this.data('stellar-backgroundStartingLeft'), $this.data('stellar-backgroundStartingTop'));
}
// Catch-all for margin top/left properties (these evaluate to 'auto' in IE7 and IE8)
marginLeft = ($this.css('margin-left') === 'auto') ? 0 : parseInt($this.css('margin-left'), 10);
marginTop = ($this.css('margin-top') === 'auto') ? 0 : parseInt($this.css('margin-top'), 10);
offsetLeft = $this.offset().left - marginLeft - scrollLeft;
offsetTop = $this.offset().top - marginTop - scrollTop;
// Calculate the offset parent
$this.parents().each(function() {
var $this = $(this);
if ($this.data('stellar-offset-parent') === true) {
parentOffsetLeft = tempParentOffsetLeft;
parentOffsetTop = tempParentOffsetTop;
$offsetParent = $this;
return false;
} else {
tempParentOffsetLeft += $this.position().left;
tempParentOffsetTop += $this.position().top;
}
});
// Detect the offsets
horizontalOffset = ($this.data('stellar-horizontal-offset') !== undefined ? $this.data('stellar-horizontal-offset') : ($offsetParent !== undefined && $offsetParent.data('stellar-horizontal-offset') !== undefined ? $offsetParent.data('stellar-horizontal-offset') : self.horizontalOffset));
verticalOffset = ($this.data('stellar-vertical-offset') !== undefined ? $this.data('stellar-vertical-offset') : ($offsetParent !== undefined && $offsetParent.data('stellar-vertical-offset') !== undefined ? $offsetParent.data('stellar-vertical-offset') : self.verticalOffset));
self.backgrounds.push({
$element: $this,
$offsetParent: $offsetParent,
isFixed: $this.css('background-attachment') === 'fixed',
horizontalOffset: horizontalOffset,
verticalOffset: verticalOffset,
startingValueLeft: backgroundPosition[0],
startingValueTop: backgroundPosition[1],
startingBackgroundPositionLeft: (isNaN(parseInt(backgroundPosition[0], 10)) ? 0 : parseInt(backgroundPosition[0], 10)),
startingBackgroundPositionTop: (isNaN(parseInt(backgroundPosition[1], 10)) ? 0 : parseInt(backgroundPosition[1], 10)),
startingPositionLeft: $this.position().left,
startingPositionTop: $this.position().top,
startingOffsetLeft: offsetLeft,
startingOffsetTop: offsetTop,
parentOffsetLeft: parentOffsetLeft,
parentOffsetTop: parentOffsetTop,
stellarRatio: ($this.data('stellar-background-ratio') === undefined ? 1 : $this.data('stellar-background-ratio'))
});
});
},
_reset: function() {
var particle,
startingPositionLeft,
startingPositionTop,
background,
i;
for (i = this.particles.length - 1; i >= 0; i--) {
particle = this.particles[i];
startingPositionLeft = particle.$element.data('stellar-startingLeft');
startingPositionTop = particle.$element.data('stellar-startingTop');
this._setPosition(particle.$element, startingPositionLeft, startingPositionLeft, startingPositionTop, startingPositionTop);
this.options.showElement(particle.$element);
particle.$element.data('stellar-startingLeft', null).data('stellar-elementIsActive', null).data('stellar-backgroundIsActive', null);
}
for (i = this.backgrounds.length - 1; i >= 0; i--) {
background = this.backgrounds[i];
background.$element.data('stellar-backgroundStartingLeft', null).data('stellar-backgroundStartingTop', null);
setBackgroundPosition(background.$element, background.startingValueLeft, background.startingValueTop);
}
},
destroy: function() {
this._reset();
this.$scrollElement.unbind('resize.' + this.name).unbind('scroll.' + this.name);
this._animationLoop = $.noop;
$(window).unbind('load.' + this.name).unbind('resize.' + this.name);
},
_setOffsets: function() {
var self = this,
$window = $(window);
$window.unbind('resize.horizontal-' + this.name).unbind('resize.vertical-' + this.name);
if (typeof this.options.horizontalOffset === 'function') {
this.horizontalOffset = this.options.horizontalOffset();
$window.bind('resize.horizontal-' + this.name, function() {
self.horizontalOffset = self.options.horizontalOffset();
});
} else {
this.horizontalOffset = this.options.horizontalOffset;
}
if (typeof this.options.verticalOffset === 'function') {
this.verticalOffset = this.options.verticalOffset();
$window.bind('resize.vertical-' + this.name, function() {
self.verticalOffset = self.options.verticalOffset();
});
} else {
this.verticalOffset = this.options.verticalOffset;
}
},
_repositionElements: function() {
var scrollLeft = this._getScrollLeft(),
scrollTop = this._getScrollTop(),
horizontalOffset,
verticalOffset,
particle,
fixedRatioOffset,
background,
bgLeft,
bgTop,
isVisibleVertical = true,
isVisibleHorizontal = true,
newPositionLeft,
newPositionTop,
newOffsetLeft,
newOffsetTop,
i;
// First check that the scroll position or container size has changed
if (this.currentScrollLeft === scrollLeft && this.currentScrollTop === scrollTop && this.currentWidth === this.viewportWidth && this.currentHeight === this.viewportHeight) {
return;
} else {
this.currentScrollLeft = scrollLeft;
this.currentScrollTop = scrollTop;
this.currentWidth = this.viewportWidth;
this.currentHeight = this.viewportHeight;
}
// Reposition elements
for (i = this.particles.length - 1; i >= 0; i--) {
particle = this.particles[i];
fixedRatioOffset = (particle.isFixed ? 1 : 0);
// Calculate position, then calculate what the particle's new offset will be (for visibility check)
if (this.options.horizontalScrolling) {
newPositionLeft = (scrollLeft + particle.horizontalOffset + this.viewportOffsetLeft + particle.startingPositionLeft - particle.startingOffsetLeft + particle.parentOffsetLeft) * -(particle.stellarRatio + fixedRatioOffset - 1) + particle.startingPositionLeft;
newOffsetLeft = newPositionLeft - particle.startingPositionLeft + particle.startingOffsetLeft;
} else {
newPositionLeft = particle.startingPositionLeft;
newOffsetLeft = particle.startingOffsetLeft;
}
if (this.options.verticalScrolling) {
newPositionTop = (scrollTop + particle.verticalOffset + this.viewportOffsetTop + particle.startingPositionTop - particle.startingOffsetTop + particle.parentOffsetTop) * -(particle.stellarRatio + fixedRatioOffset - 1) + particle.startingPositionTop;
newOffsetTop = newPositionTop - particle.startingPositionTop + particle.startingOffsetTop;
} else {
newPositionTop = particle.startingPositionTop;
newOffsetTop = particle.startingOffsetTop;
}
// Check visibility
if (this.options.hideDistantElements) {
isVisibleHorizontal = !this.options.horizontalScrolling || newOffsetLeft + particle.width > (particle.isFixed ? 0 : scrollLeft) && newOffsetLeft < (particle.isFixed ? 0 : scrollLeft) + this.viewportWidth + this.viewportOffsetLeft;
isVisibleVertical = !this.options.verticalScrolling || newOffsetTop + particle.height > (particle.isFixed ? 0 : scrollTop) && newOffsetTop < (particle.isFixed ? 0 : scrollTop) + this.viewportHeight + this.viewportOffsetTop;
}
if (isVisibleHorizontal && isVisibleVertical) {
if (particle.isHidden) {
this.options.showElement(particle.$element);
particle.isHidden = false;
}
this._setPosition(particle.$element, newPositionLeft, particle.startingPositionLeft, newPositionTop, particle.startingPositionTop);
} else {
if (!particle.isHidden) {
this.options.hideElement(particle.$element);
particle.isHidden = true;
}
}
}
// Reposition backgrounds
for (i = this.backgrounds.length - 1; i >= 0; i--) {
background = this.backgrounds[i];
fixedRatioOffset = (background.isFixed ? 0 : 1);
bgLeft = (this.options.horizontalScrolling ? (scrollLeft + background.horizontalOffset - this.viewportOffsetLeft - background.startingOffsetLeft + background.parentOffsetLeft - background.startingBackgroundPositionLeft) * (fixedRatioOffset - background.stellarRatio) + 'px' : background.startingValueLeft);
bgTop = (this.options.verticalScrolling ? (scrollTop + background.verticalOffset - this.viewportOffsetTop - background.startingOffsetTop + background.parentOffsetTop - background.startingBackgroundPositionTop) * (fixedRatioOffset - background.stellarRatio) + 'px' : background.startingValueTop);
setBackgroundPosition(background.$element, bgLeft, bgTop);
}
},
_handleScrollEvent: function() {
var self = this,
ticking = false;
var update = function() {
self._repositionElements();
ticking = false;
};
var requestTick = function() {
if (!ticking) {
requestAnimFrame(update);
ticking = true;
}
};
this.$scrollElement.bind('scroll.' + this.name, requestTick);
requestTick();
},
_startAnimationLoop: function() {
var self = this;
this._animationLoop = function() {
requestAnimFrame(self._animationLoop);
self._repositionElements();
};
this._animationLoop();
}
};
$.fn[pluginName] = function (options) {
var args = arguments;
if (options === undefined || typeof options === 'object') {
return this.each(function () {
if (!$.data(this, 'plugin_' + pluginName)) {
$.data(this, 'plugin_' + pluginName, new Plugin(this, options));
}
});
} else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
return this.each(function () {
var instance = $.data(this, 'plugin_' + pluginName);
if (instance instanceof Plugin && typeof instance[options] === 'function') {
instance[options].apply(instance, Array.prototype.slice.call(args, 1));
}
if (options === 'destroy') {
$.data(this, 'plugin_' + pluginName, null);
}
});
}
};
$[pluginName] = function(options) {
var $window = $(window);
return $window.stellar.apply($window, Array.prototype.slice.call(arguments, 0));
};
// Expose the scroll and position property function hashes so they can be extended
$[pluginName].scrollProperty = scrollProperty;
$[pluginName].positionProperty = positionProperty;
// Expose the plugin class so it can be modified
window.Stellar = Plugin;
}(jQuery, this, document));

File diff suppressed because one or more lines are too long

View File

@ -1,46 +0,0 @@
{
"name": "jquery.stellar",
"title": "Stellar.js",
"version": "0.6.2",
"description": "Parallax scrolling made easy.",
"homepage": "http://markdalgleish.com/projects/stellar.js",
"author": {
"name": "Mark Dalgleish",
"url": "http://markdalgleish.com"
},
"keywords": [
"parallax",
"scroll",
"effect",
"animation"
],
"licenses": [
{
"type": "MIT",
"url": "http://markdalgleish.mit-license.org"
}
],
"dependencies": {
"jquery": ">=1.4.3"
},
"bugs": "https://github.com/markdalgleish/stellar.js/issues",
"repository": {
"type": "git",
"url": "git://github.com/markdalgleish/stellar.js.git"
},
"maintainers": [
{
"name": "Mark Dalgleish",
"url": "http://markdalgleish.com"
}
],
"files": [
"jquery.stellar.js"
],
"scripts": {
"test": "grunt test"
},
"devDependencies": {
"grunt": "~0.3.17"
}
}

View File

@ -1,29 +0,0 @@
{
"name": "stellar",
"title": "Stellar.js",
"version": "0.6.2",
"description": "Parallax scrolling made easy.",
"homepage": "http://markdalgleish.com/projects/stellar.js",
"author": {
"name": "Mark Dalgleish",
"url": "http://markdalgleish.com"
},
"keywords": [
"parallax",
"scroll",
"effect",
"animation"
],
"licenses": [
{
"type": "MIT",
"url": "http://markdalgleish.mit-license.org"
}
],
"dependencies": {
"jquery": ">=1.4.3"
},
"bugs": "https://github.com/markdalgleish/stellar.js/issues",
"docs": "http://markdalgleish.com/projects/stellar.js/docs",
"download": "https://github.com/markdalgleish/stellar.js#download"
}

View File

@ -113,7 +113,7 @@ table.editorbar-panel td.selected {
.oe_translate_or {
color: white;
padding: 0 0.2em;
padding: 0 0 0 1em;
}
.oe_translate_examples li {
@ -141,6 +141,84 @@ table.editorbar-panel td.selected {
background: #ffffb6;
}
div.oe_menu_buttons {
top: -8px;
right: -8px;
}
ul.oe_menu_editor > li:first-child > div > i:before {
content: "\f015";
}
ul.oe_menu_editor, ul.oe_menu_editor ul {
list-style-type: none;
margin: 0;
padding: 0;
}
ul.oe_menu_editor ul {
padding-left: 30px;
}
ul.oe_menu_editor button {
margin-left: 4px;
}
ul.oe_menu_editor li {
margin: 5px 0 0 0;
padding: 0;
}
ul.oe_menu_editor .oe_menu_placeholder {
outline: 1px dashed #4183c4;
}
ul.oe_menu_editor .mjs-nestedSortable-error {
background: #fbe3e4;
border-color: transparent;
}
ul.oe_menu_editor li div {
border: 1px solid #d4d4d4;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
border-color: #d4d4d4 #d4d4d4 #bcbcbc;
padding: 6px;
margin: 0;
cursor: move;
background: #f6f6f6;
background: -moz-linear-gradient(top, white 0%, #f6f6f6 47%, #ededed 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(47%, #f6f6f6), color-stop(100%, #ededed));
background: -webkit-linear-gradient(top, white 0%, #f6f6f6 47%, #ededed 100%);
background: -o-linear-gradient(top, white 0%, #f6f6f6 47%, #ededed 100%);
background: -ms-linear-gradient(top, white 0%, #f6f6f6 47%, #ededed 100%);
background: linear-gradient(to bottom, #ffffff 0%, #f6f6f6 47%, #ededed 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#ededed',GradientType=0 );
}
ul.oe_menu_editor li.mjs-nestedSortable-branch div {
background: -moz-linear-gradient(top, white 0%, #f6f6f6 47%, #f0ece9 100%);
background: -webkit-linear-gradient(top, white 0%, #f6f6f6 47%, #f0ece9 100%);
}
ul.oe_menu_editor li.mjs-nestedSortable-leaf div {
background: -moz-linear-gradient(top, white 0%, #f6f6f6 47%, #bcccbc 100%);
background: -webkit-linear-gradient(top, white 0%, #f6f6f6 47%, #bcccbc 100%);
}
ul.oe_menu_editor li.mjs-nestedSortable-collapsed.mjs-nestedSortable-hovering div {
border-color: #999999;
background: #fafafa;
}
ul.oe_menu_editor .disclose {
cursor: pointer;
width: 10px;
display: none;
}
ul.oe_menu_editor li.mjs-nestedSortable-collapsed > ul {
display: none;
}
ul.oe_menu_editor li.mjs-nestedSortable-branch > div > .disclose {
display: inline-block;
}
ul.oe_menu_editor li.mjs-nestedSortable-collapsed > div > .disclose > span:before {
content: "+ ";
}
ul.oe_menu_editor li.mjs-nestedSortable-expanded > div > .disclose > span:before {
content: "- ";
}
/* ---- RTE ---- */
.oe_editable .btn {
-webkit-user-select: auto;
@ -160,6 +238,14 @@ table.editorbar-panel td.selected {
margin-bottom: 0.5em;
}
.modal.nosave .modal-footer button.save {
display: none;
}
.modal.nosave .modal-footer button.wait {
display: inline-block !important;
visibility: visible !important;
}
.cke_widget_wrapper {
position: static !important;
}

View File

@ -108,7 +108,7 @@ table.editorbar-panel
// ---- TRANSLATIONS ---- {{{
.oe_translate_or
color: white
padding: 0 0.2em
padding: 0 0 0 1em
.oe_translate_examples li
margin: 10px
padding: 4px
@ -124,6 +124,84 @@ table.editorbar-panel
background: rgb(255, 255, 182)
// }}}
// -------- MENU -------- {{{
div.oe_menu_buttons
top: -8px
right: -8px
ul.oe_menu_editor
> li:first-child > div > i:before
content: "\f015"
&, & ul
list-style-type: none
margin: 0
padding: 0
& ul
padding-left: 30px
button
margin-left: 4px
li
margin: 5px 0 0 0
padding: 0
.oe_menu_placeholder
outline: 1px dashed #4183C4
.mjs-nestedSortable-error
background: #fbe3e4
border-color: transparent
// TODO: use compass mixins
li div
border: 1px solid #d4d4d4
-webkit-border-radius: 3px
-moz-border-radius: 3px
border-radius: 3px
border-color: #D4D4D4 #D4D4D4 #BCBCBC
padding: 6px
margin: 0
cursor: move
background: #f6f6f6
background: -moz-linear-gradient(top, #ffffff 0%, #f6f6f6 47%, #ededed 100%)
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(47%,#f6f6f6), color-stop(100%,#ededed))
background: -webkit-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#ededed 100%)
background: -o-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#ededed 100%)
background: -ms-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#ededed 100%)
background: linear-gradient(to bottom, #ffffff 0%,#f6f6f6 47%,#ededed 100%)
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#ededed',GradientType=0 )
li.mjs-nestedSortable-branch div
background: -moz-linear-gradient(top, #ffffff 0%, #f6f6f6 47%, #f0ece9 100%)
background: -webkit-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#f0ece9 100%)
li.mjs-nestedSortable-leaf div
background: -moz-linear-gradient(top, #ffffff 0%, #f6f6f6 47%, #bcccbc 100%)
background: -webkit-linear-gradient(top, #ffffff 0%,#f6f6f6 47%,#bcccbc 100%)
li.mjs-nestedSortable-collapsed.mjs-nestedSortable-hovering div
border-color: #999
background: #fafafa
.disclose
cursor: pointer
width: 10px
display: none
li.mjs-nestedSortable-collapsed > ul
display: none
li.mjs-nestedSortable-branch > div > .disclose
display: inline-block
li.mjs-nestedSortable-collapsed > div > .disclose > span:before
content: '+ '
li.mjs-nestedSortable-expanded > div > .disclose > span:before
content: '- '
// }}}
/* ---- RTE ---- */
// bootstrap makes .btn elements unselectable -> RTE double-click can't know
@ -141,6 +219,13 @@ table.editorbar-panel
.modal .image-preview
margin-bottom: 0.5em
.modal.nosave .modal-footer
button.save
display: none
button.wait
display: inline-block !important
visibility: visible !important
// wrapper positioned relatively for drag&drop widget which is disabled below.
// Breaks completely horribly crazy products listing page, so take it out.
.cke_widget_wrapper

View File

@ -2,7 +2,7 @@
/* THIS CSS FILE IS FOR WEBSITE THEMING CUSTOMIZATION ONLY
*
* css for editor buttons, openerp widget included in the website and other
* stuff must go to the editor.css
* stuff must go to the editor.css
*
*/
/* ----- GENERIC LAYOUTING HELPERS ---- */
@ -239,7 +239,7 @@ footer {
}
.oe_structure.oe_empty:empty:before, [data-oe-type=html]:empty:before, .oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child:before, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child:before {
content: "Click Edit To Create Content";
content: "Press The Top-Left Edit Button";
text-align: center;
display: block;
padding-top: 160px;
@ -361,8 +361,7 @@ footer {
}
.parallax {
background-attachment: fixed;
background-size: 100%;
position: relative;
}
.parallax.oe_small {
height: 200px;
@ -440,3 +439,28 @@ a[data-publish][data-publish='on']:hover .css_published {
::selection {
background: rgba(150, 150, 220, 0.3);
}
.logo-img {
width: 220px;
}
.oe_demo {
position: relative;
}
.oe_demo img {
width: 100%;
}
.oe_demo div {
position: absolute;
left: 0;
background-color: rgba(0, 0, 0, 0.4);
opacity: 0.85;
bottom: 0px;
width: 100%;
padding: 7px;
color: white;
font-weight: bold;
}
.oe_demo div a {
color: white;
}

View File

@ -1,10 +1,10 @@
@charset "utf-8"
/*
/*
* THIS CSS FILE IS FOR WEBSITE THEMING CUSTOMIZATION ONLY
*
* css for editor buttons, openerp widget included in the website and other
* stuff must go to the editor.css
* stuff must go to the editor.css
*
*/
@ -133,7 +133,7 @@ html,body, #wrapwrap
header, #wrap, footer
display: table-row
footer
height: 100%
background: rgb(239, 248, 248)
@ -164,7 +164,7 @@ footer
position: static
.oe_structure.oe_empty:empty:before, [data-oe-type=html]:empty:before, .oe_structure.oe_empty > .oe_drop_zone.oe_insert:only-child:before, [data-oe-type=html] > .oe_drop_zone.oe_insert:only-child:before
content: 'Click Edit To Create Content'
content: 'Press The Top-Left Edit Button'
text-align: center
display: block
padding-top: 160px
@ -183,7 +183,7 @@ footer
// .navbar .nav > li a
// text-shadow: none
// .nav > li a
// .nav > li a
// display: block
.carousel-inner .item
@ -276,15 +276,13 @@ footer
background-color: grey
.parallax
background-attachment: fixed
background-size: 100%
position: relative
&.oe_small
height: 200px
&.oe_medium
height: 300px
&.oe_big
height: 450px
/* -- Hack for removing double scrollbar from mobile preview -- */
div#mobile-preview.modal
overflow: hidden
@ -335,3 +333,24 @@ a[data-publish]
::selection
background: rgba(150, 150, 220, 0.3)
.logo-img
width: 220px
.oe_demo
position: relative
img
width: 100%
div
position: absolute
left: 0
background-color: rgba(0,0,0,0.4)
opacity: 0.85
bottom: 0px
width: 100%
padding: 7px
color: white
font-weight: bold
a
color: white

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 190 KiB

View File

@ -6,7 +6,7 @@
var hash = "#advanced-view-editor";
var website = openerp.website;
website.templates.push('/website/static/src/xml/website.ace.xml');
website.add_template_file('/website/static/src/xml/website.ace.xml');
website.ready().then(function () {
if (window.location.hash.indexOf(hash) >= 0) {

View File

@ -4,17 +4,26 @@
var website = openerp.website;
// $.fn.data automatically parses value, '0'|'1' -> 0|1
website.templates.push('/website/static/src/xml/website.editor.xml');
website.add_template_file('/website/static/src/xml/website.editor.xml');
website.dom_ready.done(function () {
var is_smartphone = $(document.body)[0].clientWidth < 767;
if (!is_smartphone) {
website.ready().then(website.init_editor);
}
$(document).on('hide.bs.dropdown', '.dropdown', function (ev) {
// Prevent dropdown closing when a contenteditable children is focused
if (ev.originalEvent
&& $(ev.target).has(ev.originalEvent.target).length
&& $(ev.originalEvent.target).is('[contenteditable]')) {
ev.preventDefault();
}
});
});
function link_dialog(editor) {
return new website.editor.LinkDialog(editor).appendTo(document.body);
return new website.editor.RTELinkDialog(editor).appendTo(document.body);
}
function image_dialog(editor) {
return new website.editor.RTEImageDialog(editor).appendTo(document.body);
@ -54,6 +63,39 @@
link_dialog(editor);
}, null, null, 500);
var previousSelection;
editor.on('selectionChange', function (evt) {
var selected = evt.data.path.lastElement;
if (previousSelection) {
// cleanup previous selection
$(previousSelection).next().remove();
previousSelection = null;
}
if (!selected.is('img')
|| selected.data('cke-realelement')
|| selected.isReadOnly()
|| selected.data('oe-model') === 'ir.ui.view') {
return;
}
// display button
var $el = $(previousSelection = selected.$);
var $btn = $('<button type="button" class="btn btn-primary" contenteditable="false">Edit</button>')
.insertAfter($el)
.click(function (e) {
e.preventDefault();
e.stopPropagation();
image_dialog(editor);
});
var position = $el.position();
$btn.css({
position: 'absolute',
top: $el.height() / 2 + position.top - $btn.outerHeight() / 2,
left: $el.width() / 2 + position.left - $btn.outerWidth() / 2,
});
});
//noinspection JSValidateTypes
editor.addCommand('link', {
exec: function (editor) {
@ -360,8 +402,10 @@
}
});
// Adding Static Menus
menu.append('<li class="divider"></li><li class="js_change_theme"><a href="/page/website.themes">Change Theme</a></li>');
menu.append('<li class="divider"></li><li><a data-action="ace" href="#">Advanced view editor</a></li>');
menu.append('<li class="divider"></li>');
menu.append('<li><a data-action="ace" href="#">HTML Editor</a></li>');
menu.append('<li class="js_change_theme"><a href="/page/website.themes">Change Theme</a></li>');
menu.append('<li><a href="/web#action=website.action_module_website">Install Apps</a></li>');
self.trigger('rte:customize_menu_ready');
}
);
@ -805,6 +849,106 @@
this.pages = Object.create(null);
this.text = null;
},
start: function () {
var self = this;
return $.when(
this.fetch_pages().done(this.proxy('fill_pages')),
this._super()
).done(function () {
self.bind_data();
});
},
save: function () {
var self = this, _super = this._super.bind(this);
var $e = this.$('.list-group-item.active .url-source');
var val = $e.val();
if (!val || !$e[0].checkValidity()) {
// FIXME: error message
$e.closest('.form-group').addClass('has-error');
$e.focus();
return;
}
var done = $.when();
if ($e.hasClass('email-address')) {
this.make_link('mailto:' + val, false, val);
} else if ($e.hasClass('existing')) {
self.make_link(val, false, this.pages[val]);
} else if ($e.hasClass('pages')) {
// Create the page, get the URL back
done = $.get(_.str.sprintf(
'/pagenew/%s?noredirect', encodeURI(val)))
.then(function (response) {
self.make_link(response, false, val);
});
} else {
this.make_link(val, this.$('input.window-new').prop('checked'));
}
done.then(_super);
},
make_link: function (url, new_window, label) {
},
bind_data: function (text, href, new_window) {
href = href || this.element && (this.element.data( 'cke-saved-href')
|| this.element.getAttribute('href'));
if (!href) { return; }
if (new_window === undefined) {
new_window = this.element.getAttribute('target') === '_blank';
}
if (text === undefined) {
text = this.element.getText();
}
var match, $control;
if ((match = /mailto:(.+)/.exec(href))) {
$control = this.$('input.email-address').val(match[1]);
} else if (href in this.pages) {
$control = this.$('select.existing').val(href);
} else if ((match = /\/page\/(.+)/.exec(href))) {
var actual_href = '/page/website.' + match[1];
if (actual_href in this.pages) {
$control = this.$('select.existing').val(actual_href);
}
}
if (!$control) {
$control = this.$('input.url').val(href);
}
this.changed($control);
this.$('input#link-text').val(text);
this.$('input.window-new').prop('checked', new_window);
},
changed: function ($e) {
this.$('.url-source').not($e).val('');
$e.closest('.list-group-item')
.addClass('active')
.siblings().removeClass('active')
.addBack().removeClass('has-error');
},
fetch_pages: function () {
return openerp.jsonRpc('/web/dataset/call_kw', 'call', {
model: 'website',
method: 'list_pages',
args: [null],
kwargs: {
context: website.get_context()
},
});
},
fill_pages: function (results) {
var self = this;
var pages = this.$('select.existing')[0];
_(results).each(function (result) {
self.pages[result.url] = result.name;
pages.options[pages.options.length] =
new Option(result.name, result.url);
});
},
});
website.editor.RTELinkDialog = website.editor.LinkDialog.extend({
start: function () {
var element;
if ((element = this.get_selected_link()) && element.hasAttribute('href')) {
@ -815,10 +959,7 @@
this.add_removal_button();
}
return $.when(
this.fetch_pages().done(this.proxy('fill_pages')),
this._super()
).done(this.proxy('bind_data'));
return this._super();
},
add_removal_button: function () {
this.$('.modal-footer').prepend(
@ -884,66 +1025,6 @@
}, 0);
}
},
save: function () {
var self = this, _super = this._super.bind(this);
var $e = this.$('.list-group-item.active .url-source');
var val = $e.val();
if (!val || !$e[0].checkValidity()) {
// FIXME: error message
$e.closest('.form-group').addClass('has-error');
return;
}
var done = $.when();
if ($e.hasClass('email-address')) {
this.make_link('mailto:' + val, false, val);
} else if ($e.hasClass('existing')) {
self.make_link(val, false, this.pages[val]);
} else if ($e.hasClass('pages')) {
// Create the page, get the URL back
done = $.get(_.str.sprintf(
'/pagenew/%s?noredirect', encodeURI(val)))
.then(function (response) {
self.make_link(response, false, val);
});
} else {
this.make_link(val, this.$('input.window-new').prop('checked'));
}
done.then(_super);
},
bind_data: function () {
var href = this.element && (this.element.data( 'cke-saved-href')
|| this.element.getAttribute('href'));
if (!href) { return; }
var match, $control;
if (match = /mailto:(.+)/.exec(href)) {
$control = this.$('input.email-address').val(match[1]);
} else if (href in this.pages) {
$control = this.$('select.existing').val(href);
} else if (match = /\/page\/(.+)/.exec(href)) {
var actual_href = '/page/website.' + match[1];
if (actual_href in this.pages) {
$control = this.$('select.existing').val(actual_href);
}
}
if (!$control) {
$control = this.$('input.url').val(href);
}
this.changed($control);
this.$('input#link-text').val(this.element.getText());
this.$('input.window-new').prop(
'checked', this.element.getAttribute('target') === '_blank');
},
changed: function ($e) {
this.$('.url-source').not($e).val('');
$e.closest('.list-group-item')
.addClass('active')
.siblings().removeClass('active')
.addBack().removeClass('has-error');
},
/**
* CKEDITOR.plugins.link.getSelectedLink ignores the editor's root,
* if the editor is set directly on a link it will thus not work.
@ -951,27 +1032,8 @@
get_selected_link: function () {
return get_selected_link(this.editor);
},
fetch_pages: function () {
return openerp.jsonRpc('/web/dataset/call_kw', 'call', {
model: 'website',
method: 'list_pages',
args: [null],
kwargs: {
context: website.get_context()
},
});
},
fill_pages: function (results) {
var self = this;
var pages = this.$('select.existing')[0];
_(results).each(function (result) {
self.pages[result.url] = result.name;
pages.options[pages.options.length] =
new Option(result.name, result.url);
});
},
});
/**
* ImageDialog widget. Lets users change an image, including uploading a
* new image in OpenERP or selecting the image style (if supported by
@ -1003,6 +1065,7 @@
}),
start: function () {
this.$('.modal-footer [disabled]').text("Uploading…");
var $options = this.$('.image-style').children();
this.image_styles = $options.map(function () { return this.value; }).get();
@ -1038,6 +1101,7 @@
},
file_selection: function () {
this.$el.addClass('nosave');
this.$('button.filepicker').removeClass('btn-danger btn-success');
var self = this;
@ -1060,6 +1124,7 @@
this.set_image(url);
},
preview_image: function () {
this.$el.removeClass('nosave');
var image = this.$('input.url').val();
if (!image) { return; }

View File

@ -5,35 +5,30 @@
// The following line can be removed in 2017
openerp.website = website;
var templates = website.templates = [
'/website/static/src/xml/website.xml'
];
website.get_context = function (dict) {
var html = document.documentElement;
return _.extend({
lang: html.getAttribute('lang').replace('-', '_')
lang: html.getAttribute('lang').replace('-', '_'),
website_id: html.getAttribute('data-website-id')|0
}, dict);
};
/* ----- TEMPLATE LOADING ---- */
website.add_template = function(template) {
templates.push(template);
};
website.load_templates = function(templates) {
var dones = _(templates).map(function (t) {
return new $.Deferred(function (d) {
openerp.qweb.add_template(t, function(err) {
if (err) {
d.reject(err);
} else {
d.resolve();
}
});
var templates_def = $.Deferred().resolve();
website.add_template_file = function(template) {
templates_def = templates_def.then(function() {
var def = $.Deferred();
openerp.qweb.add_template(template, function(err) {
if (err) {
def.reject(err);
} else {
def.resolve();
}
});
return def;
});
return $.when.apply(null, dones);
};
website.add_template_file('/website/static/src/xml/website.xml');
website.reload = function () {
location.hash = "scrollTop=" + window.document.body.scrollTop;
if (location.search.indexOf("enable_editor") > -1) {
@ -110,20 +105,33 @@
*/
website.ready = function() {
if (!all_ready) {
var tpl = website.load_templates(templates);
all_ready = dom_ready.then(function () {
all_ready = $.when(dom_ready, templates_def).then(function () {
if ($('html').data('editable')) {
website.id = $('html').data('website-id');
website.session = new openerp.Session();
var modules = ['website'];
return openerp._t.database.load_translations(website.session, modules, website.get_context().lang);
}
}).then(tpl).promise();
}).promise();
}
return all_ready;
};
website.error = function(data, url) {
var $error = $(openerp.qweb.render('website.error_dialog', {
'title': data.data ? data.data.arguments[0] : data.statusText,
'message': data.data ? data.data.arguments[1] : "",
'backend_url': url
}));
$error.appendTo("body");
$error.modal('show');
};
dom_ready.then(function () {
/* ----- BOOTSTRAP STUFF ---- */
$('.js_tooltip').bstooltip();
/* ----- PUBLISHING STUFF ---- */
$('[data-publish]:has(.js_publish)').each(function () {
var $pub = $("[data-publish]", this);
@ -140,12 +148,15 @@
$(document).on('click', '.js_publish', function (e) {
e.preventDefault();
var $data = $(":first", this).parents("[data-publish]");
$data.attr("data-publish", $data.first().attr("data-publish") == 'off' ? 'on' : 'off');
openerp.jsonRpc('/website/publish', 'call', {'id': $(this).data('id'), 'object': $(this).data('object')})
var $a = $(this);
var $data = $a.find(":first").parents("[data-publish]");
openerp.jsonRpc($a.data('controller') || '/website/publish', 'call', {'id': +$a.data('id'), 'object': $a.data('object')})
.then(function (result) {
$data.attr("data-publish", +result ? 'on' : 'off');
}).fail(function (err, data) {
website.error(data, '/web#model='+$a.data('object')+'&id='+$a.data('id'));
});
return false;
});
$(document).on('click', '.js_publish_management .js_publish_btn', function () {
@ -156,11 +167,13 @@
$data.toggleClass("css_unpublish css_publish");
$btn.removeClass("btn-default btn-success");
openerp.jsonRpc('/website/publish', 'call', {'id': +$data.data('id'), 'object': $data.data('object')})
openerp.jsonRpc($data.data('controller') || '/website/publish', 'call', {'id': +$data.data('id'), 'object': $data.data('object')})
.then(function (result) {
$btn.toggleClass("btn-default", !result).toggleClass("btn-success", result);
$data.toggleClass("css_unpublish", !result).toggleClass("css_publish", result);
$data.parents("[data-publish]").attr("data-publish", +result ? 'on' : 'off');
}).fail(function (err, data) {
website.error(data, '/web#model='+$data.data('object')+'&id='+$data.data('id'));
});
});

View File

@ -0,0 +1,192 @@
(function () {
'use strict';
var website = openerp.website;
website.menu = {};
website.add_template_file('/website/static/src/xml/website.menu.xml');
website.EditorBar.include({
events: _.extend({}, website.EditorBar.prototype.events, {
'click a[data-action="edit-structure"]': 'editStructure',
}),
editStructure: function () {
var context = website.get_context();
openerp.jsonRpc('/web/dataset/call_kw', 'call', {
model: 'website.menu',
method: 'get_tree',
args: [[context.website_id]],
kwargs: {
context: context
},
}).then(function (menu) {
return new website.menu.EditMenuDialog(menu).appendTo(document.body);
});
},
});
website.menu.EditMenuDialog = website.editor.Dialog.extend({
template: 'website.menu.dialog.edit',
events: _.extend({}, website.editor.Dialog.prototype.events, {
'click button.js_add_menu': 'add_menu',
'click button.js_edit_menu': 'edit_menu',
'click button.js_delete_menu': 'delete_menu',
}),
init: function (menu) {
this.menu = menu;
this.root_menu_id = menu.id;
this.flat = this.flatenize(menu);
this.to_delete = [];
this._super();
},
start: function () {
var r = this._super.apply(this, arguments);
var button = openerp.qweb.render('website.menu.dialog.footer-button');
this.$('.modal-footer').prepend(button);
this.$('.oe_menu_editor').nestedSortable({
listType: 'ul',
handle: 'div',
items: 'li',
maxLevels: 2,
toleranceElement: '> div',
forcePlaceholderSize: true,
opacity: 0.6,
placeholder: 'oe_menu_placeholder',
tolerance: 'pointer',
attribute: 'data-menu-id',
expression: '()(.+)', // nestedSortable takes the second match of an expression (*sigh*)
});
return r;
},
flatenize: function (node, dict) {
dict = dict || {};
var self = this;
dict[node.id] = node;
node.children.forEach(function (child) {
self.flatenize(child, dict);
});
return dict;
},
add_menu: function () {
var self = this;
var dialog = new website.menu.MenuEntryDialog();
dialog.on('add-menu', this, function (link) {
var new_menu = {
id: _.uniqueId('new-'),
name: link[2] || link[0],
url: link[0],
new_window: link[1],
parent_id: false,
sequence: 0,
children: [],
};
self.flat[new_menu.id] = new_menu;
self.$('.oe_menu_editor').append(
openerp.qweb.render(
'website.menu.dialog.submenu', { submenu: new_menu }));
});
dialog.appendTo(document.body);
},
edit_menu: function (ev) {
var self = this;
var menu_id = $(ev.currentTarget).closest('[data-menu-id]').data('menu-id');
var menu = self.flat[menu_id];
if (menu) {
var dialog = new website.menu.MenuEntryDialog(undefined, menu);
dialog.on('update-menu', this, function (link) {
var id = link.shift();
var menu_obj = self.flat[id];
_.extend(menu_obj, {
name: link[2],
url: link[0],
new_window: link[1],
});
var $menu = self.$('[data-menu-id="' + id + '"]');
$menu.find('> div > span').text(menu_obj.name);
});
dialog.appendTo(document.body);
} else {
alert("Could not find menu entry");
}
},
delete_menu: function (ev) {
var self = this;
var $menu = $(ev.currentTarget).closest('[data-menu-id]');
var mid = $menu.data('menu-id')|0;
if (mid) {
this.to_delete.push(mid);
}
$menu.remove();
},
save: function () {
var self = this;
var new_menu = this.$('.oe_menu_editor').nestedSortable('toArray', {startDepthCount: 0});
var levels = [];
var data = [];
var context = website.get_context();
// Resquence, re-tree and remove useless data
new_menu.forEach(function (menu) {
if (menu.item_id) {
levels[menu.depth] = (levels[menu.depth] || 0) + 1;
var mobj = self.flat[menu.item_id];
mobj.sequence = levels[menu.depth];
mobj.parent_id = (menu.parent_id|0) || menu.parent_id || self.root_menu_id;
delete(mobj.children);
data.push(mobj);
}
});
openerp.jsonRpc('/web/dataset/call_kw', 'call', {
model: 'website.menu',
method: 'save',
args: [[context.website_id], { data: data, to_delete: self.to_delete }],
kwargs: {
context: context
},
}).then(function (menu) {
self.close();
website.reload();
});
},
});
website.menu.MenuEntryDialog = website.editor.LinkDialog.extend({
template: 'website.menu.dialog.add',
init: function (editor, data) {
this.data = data;
this.update_mode = !!this.data;
return this._super.apply(this, arguments);
},
start: function () {
var self = this;
return $.when(this._super.apply(this, arguments)).then(function () {
if (self.data) {
self.bind_data(self.data.name, self.data.url, self.data.new_window);
}
var $link_text = self.$('#link-text').focus();
self.$('#link-existing').change(function () {
if (!$link_text.val()) {
$link_text.val($(this).find('option:selected').text());
$link_text.focus();
}
});
});
},
save: function () {
var $e = this.$('#link-text');
if (!$e.val() || !$e[0].checkValidity()) {
$e.closest('.form-group').addClass('has-error');
$e.focus();
return;
}
return this._super.apply(this, arguments);
},
make_link: function (url, new_window, label) {
var menu_label = this.$('input#link-text').val() || label;
if (this.data) {
this.trigger('update-menu', [this.data.id, url, new_window, menu_label]);
} else {
this.trigger('add-menu', [url, new_window, menu_label]);
}
},
});
})();

View File

@ -2,7 +2,7 @@
'use strict';
var website = openerp.website;
website.templates.push('/website/static/src/xml/website.seo.xml');
website.add_template_file('/website/static/src/xml/website.seo.xml');
website.EditorBar.include({
events: _.extend({}, website.EditorBar.prototype.events, {

View File

@ -1,8 +1,23 @@
(function () {
'use strict';
var start_snippet_animation = function () {
hack_to_add_snippet_id();
$("[data-snippet-id]").each(function() {
var $snipped_id = $(this);
if ( !$snipped_id.parents("#oe_snippets").length &&
typeof $snipped_id.data("snippet-view") === 'undefined' &&
website.snippet.animationRegistry[$snipped_id.data("snippet-id")]) {
var snippet = new website.snippet.animationRegistry[$snipped_id.data("snippet-id")]($snipped_id);
$snipped_id.data("snippet-view", snippet);
}
});
};
$(document).ready(start_snippet_animation);
var website = openerp.website;
website.templates.push('/website/static/src/xml/website.snippets.xml');
website.add_template_file('/website/static/src/xml/website.snippets.xml');
website.EditorBar.include({
start: function () {
@ -17,9 +32,17 @@
return this._super();
},
edit: function () {
var self = this;
$("body").off('click');
window.snippets = this.snippets = new website.snippet.BuildingBlock(this);
this.snippets.appendTo(this.$el);
this.on('rte:ready', this, function () {
self.snippets.$button.removeClass("hidden");
start_snippet_animation();
self.trigger('rte:snippets_ready');
});
return this._super.apply(this, arguments);
},
save: function () {
@ -33,20 +56,8 @@
},
});
$(document).ready(function () {
hack_to_add_snippet_id();
$("[data-snippet-id]").each(function() {
var $snipped_id = $(this);
if (typeof $snipped_id.data("snippet-view") === 'undefined' &&
website.snippet.animationRegistry[$snipped_id.data("snippet-id")]) {
$snipped_id.data("snippet-view", new website.snippet.animationRegistry[$snipped_id.data("snippet-id")]($snipped_id));
}
});
});
/* ----- SNIPPET SELECTOR ---- */
website.snippet = {};
var observer = new website.Observer(function (mutations) {
if (!_(mutations).find(function (m) {
@ -118,10 +129,11 @@
start: function() {
var self = this;
var $ul = this.parent.$("#website-top-edit ul");
this.$button = $(openerp.qweb.render('website.snippets_button'))
.prependTo(this.parent.$("#website-top-edit ul"))
.find("button");
var $button = $(openerp.qweb.render('website.snippets_button')).prependTo($ul);
$button.find('button').click(function () {
this.$button.click(function () {
self.make_active(false);
self.$el.toggleClass("hidden");
});
@ -237,6 +249,7 @@
}
if (this.$active_snipped_id) {
this.snippet_blur(this.$active_snipped_id);
this.$active_snipped_id = false;
}
if ($snipped_id) {
if(_.indexOf(this.snippets, $snipped_id.get(0)) === -1) {
@ -245,8 +258,6 @@
this.$active_snipped_id = $snipped_id;
this.create_overlay(this.$active_snipped_id);
this.snippet_focus($snipped_id);
} else {
self.$active_snipped_id = false;
}
},
create_overlay: function ($snipped_id) {
@ -361,7 +372,7 @@
}
self.create_overlay($target);
$target.data("snippet-editor").build_snippet($target);
$target.data("snippet-editor").drop_and_build_snippet($target);
} else {
$target = $(this).data('target');
@ -369,7 +380,7 @@
self.create_overlay($target);
if (website.snippet.editorRegistry[snipped_id]) {
var snippet = new website.snippet.editorRegistry[snipped_id](self, $target);
snippet.build_snippet($target);
snippet.drop_and_build_snippet($target);
}
}
setTimeout(function () {self.make_active($target);},0);
@ -498,7 +509,7 @@
console.debug( "A good node must have a [data-oe-model] attribute or must have at least one parent with [data-oe-model] attribute.");
console.debug( "Wrong node(s): ", selector);
}
function is_visible($el){
return $el.css('display') != 'none'
&& $el.css('opacity') != '0'
@ -513,7 +524,7 @@
var parents = $(this).parents().filter(function(){ return !is_visible($(this)); });
return parents.length === 0;
});
$targets.each(function () {
var $target = $(this);
if (!$target.data('overlay')) {
@ -552,13 +563,13 @@
start: function () {
},
/* onFocusEdit
* if they are an editor for this data-snippet-id
* if they are an editor for this data-snippet-id
* Called before onFocus of snippet editor
*/
onFocusEdit : function () {},
/* onBlurEdit
* if they are an editor for this data-snippet-id
* if they are an editor for this data-snippet-id
* Called after onBlur of snippet editor
*/
onBlurEdit : function () {},
@ -589,7 +600,8 @@
* Displayed into the overlay options on focus
*/
_readXMLData: function() {
this.$el = this.parent.$snippets.siblings("[data-snippet-id='"+this.snippet_id+"']").clone();
var self = this;
this.$el = this.parent.$snippets.filter(function () { return $(this).data("snippet-id") == self.snippet_id; }).clone();
this.$editor = this.$el.find(".oe_snippet_options");
var $options = this.$overlay.find(".oe_overlay_options");
this.$editor.prependTo($options.find(".oe_options ul"));
@ -658,7 +670,7 @@
});
$("body").addClass('move-important');
self._drag_and_drop_after_insert_dropzone();
self._drag_and_drop_active_drop_zone($('.oe_drop_zone'));
},
@ -710,7 +722,7 @@
if (website.snippet.editorRegistry[snipped_id]) {
var snippet = new website.snippet.editorRegistry[snipped_id](this, this.$target);
snippet.build_snippet(this.$target);
snippet.drop_and_build_snippet(this.$target);
}
var _class = "oe_snippet_" + snipped_id + " " + ($li.data("class") || "");
if (active) {
@ -749,6 +761,7 @@
var index = _.indexOf(self.parent.snippets, self.$target.get(0));
delete self.parent.snippets[index];
self.$target.remove();
self.$overlay.remove();
return false;
});
this._drag_and_drop();
@ -756,11 +769,11 @@
},
/*
* build_snippet
* drop_and_build_snippet
* This method is called just after that a thumbnail is drag and dropped into a drop zone
* (after the insertion of this.$body, if this.$body exists)
*/
build_snippet: function ($target) {
drop_and_build_snippet: function ($target) {
},
/* onFocus
@ -847,7 +860,7 @@
if (!resize_values.s) $box.find(".oe_handle.s").remove();
if (!resize_values.e) $box.find(".oe_handle.e").remove();
if (!resize_values.w) $box.find(".oe_handle.w").remove();
this.$overlay.append($box.find(".oe_handles").html());
this.$overlay.find(".oe_handle").on('mousedown', function (event){
@ -1026,7 +1039,7 @@
},
});
website.snippet.editorRegistry.carousel = website.snippet.editorRegistry.resize.extend({
build_snippet: function() {
drop_and_build_snippet: function() {
var id = 0;
$("body .carousel").each(function () {
var _id = +$(this).attr("id").replace(/^myCarousel/, '');
@ -1037,6 +1050,7 @@
this.$target.attr("id", "myCarousel" + id);
this.$target.find(".carousel-control").attr("href", "#myCarousel" + id);
this.$target.find("[data-target='#myCarousel']").attr("data-target", "#myCarousel" + id);
this.rebind_event();
},
onFocus: function () {
@ -1053,6 +1067,9 @@
if(!this.$target.find(".item.active").length) {
this.$target.find(".item:first").addClass("active");
}
this.$target.removeAttr('contentEditable')
.find('.content, .carousel-image img')
.removeAttr('contentEditable');
},
start : function () {
this._super();
@ -1087,6 +1104,10 @@
self.$target.carousel($(this).data('slide')); });
this.$target.off('click').on('click', '.carousel-indicators [data-target]', function () {
self.$target.carousel(+$(this).data('slide-to')); });
this.$target.attr('contentEditable', 'false')
.find('.content, .carousel-image img')
.attr('contentEditable', 'true');
},
on_add: function (e) {
e.preventDefault();
@ -1095,7 +1116,7 @@
var index = $active.index();
this.$target.find('.carousel-control, .carousel-indicators').removeClass("hidden");
this.$indicators.append('<li data-target="#' + this.id + '" data-slide-to="' + cycle + '"></li>');
var $clone = this.$el.find(".item.active").clone();
var bg = this.$editor.find('ul[name="carousel-background"] li:not([data-value="'+ $active.css("background-image").replace(/.*:\/\/[^\/]+|\)$/g, '') +'"]):first').data("value");
$clone.css("background-image", "url('"+ bg +"')");
@ -1191,24 +1212,23 @@
website.snippet.editorRegistry.parallax = website.snippet.editorRegistry.resize.extend({
start : function () {
this._super();
this.change_background($('.parallax', this.$target), 'ul[name="parallax-background"]');
this.change_background(this.$target, 'ul[name="parallax-background"]');
this.scroll();
this.change_size();
},
scroll: function(){
scroll: function () {
var self = this;
var $ul = this.$editor.find('ul[name="parallax-scroll"]');
var $li = $ul.find("li");
var $parallax = this.$target.find('.parallax');
var speed = $parallax.data('stellar-background-ratio') || 0.5 ;
var speed = this.$target.data('scroll-background-ratio') || 0.6 ;
$ul.find('[data-value="' + speed + '"]').addClass('active');
$li.on('click', function (event) {
$li.removeClass("active");
$(this).addClass("active");
var speed = $(this).data('value');
$parallax.attr('data-stellar-background-ratio', speed);
self.$target.attr('data-scroll-background-ratio', speed);
self.$target.data("snippet-view").set_values();
});
},
clean_for_save: function () {
this._super();
@ -1216,14 +1236,12 @@
},
change_size: function () {
var self = this;
var $el = $('.oe_big,.oe_medium,.oe_small', this.$target);
var size = 'oe_big';
if ($el.hasClass('oe_small'))
if (this.$target.hasClass('oe_small'))
size = 'oe_small';
else if ($el.hasClass('oe_medium'))
else if (this.$target.hasClass('oe_medium'))
size = 'oe_medium';
var $ul = this.$editor.find('ul[name="parallax-size"]');
var $li = $ul.find("li");
@ -1232,21 +1250,50 @@
$li.on('click', function (event) {
$li.removeClass("active");
$(this).addClass("active");
self.$target.data("snippet-view").set_values();
})
.on('mouseover', function (event) {
$el.removeClass('oe_big oe_small oe_medium');
$el.addClass($(event.currentTarget).data("value"));
self.$target.removeClass('oe_big oe_small oe_medium');
self.$target.addClass($(event.currentTarget).data("value"));
})
.on('mouseout', function (event) {
$el.removeClass('oe_big oe_small oe_medium');
$el.addClass($ul.find('li.active').data("value"));
self.$target.removeClass('oe_big oe_small oe_medium');
self.$target.addClass($ul.find('li.active').data("value"));
});
}
});
website.snippet.animationRegistry.parallax = website.snippet.Animation.extend({
start: function () {
$.stellar({ horizontalScrolling: false, verticalOffset: 0 });
var self = this;
this.set_values();
var on_scroll = function () {
var speed = parseFloat(self.$target.attr("data-scroll-background-ratio") || 0);
if (speed == 1) return;
var offset = parseFloat(self.$target.attr("data-scroll-background-offset") || 0);
var top = offset + window.scrollY * speed;
self.$target.css("background-position", "0px " + top + "px");
};
$(window).off("scroll").on("scroll", on_scroll);
},
set_values: function () {
var self = this;
var speed = parseFloat(self.$target.attr("data-scroll-background-ratio") || 0);
if (speed == 1) {
this.$target.css("background-attachment", "fixed").css("background-position", "0px 0px");
return;
} else {
this.$target.css("background-attachment", "scroll");
}
this.$target.attr("data-scroll-background-offset", 0);
var img = new Image();
img.onload = function () {
self.$target.attr("data-scroll-background-offset", self.$target.outerHeight() - this.height);
$(window).scroll();
};
img.src = this.$target.css("background-image").replace(/url\(['"]*|['"]*\)/g, "");
}
});
/*

View File

@ -24,10 +24,14 @@
stepId: 'edit-page',
element: 'button[data-action=edit]',
placement: 'bottom',
reflex: true,
title: "Edit this page",
content: "Every page of your website can be modified through the <i>Edit</i> button.",
template: render('website.tour_popover'),
onShow: function () {
editor.on('rte:snippets_ready', editor, function() {
self.movetoStep('add-block');
});
},
},
{
stepId: 'add-block',
@ -37,11 +41,6 @@
content: "To add content in a page, you can insert building blocks.",
template: render('website.tour_popover'),
onShow: function () {
function refreshAddBlockStep () {
self.tour.showStep(self.indexOfStep('add-block'));
editor.off('rte:ready', editor, refreshAddBlockStep);
}
editor.on('rte:ready', editor, refreshAddBlockStep);
$('button[data-action=snippet]').click(function () {
self.movetoStep('drag-banner');
});

View File

@ -2,7 +2,7 @@
'use strict';
var website = openerp.website;
website.templates.push('/website/static/src/xml/website.tour.xml');
website.add_template_file('/website/static/src/xml/website.tour.xml');
website.tour = {
render: function render (template, dict) {

View File

@ -2,7 +2,7 @@
'use strict';
var website = openerp.website;
website.templates.push('/website/static/src/xml/website.translator.xml');
website.add_template_file('/website/static/src/xml/website.translator.xml');
var nodialog = 'website_translator_nodialog';
website.EditorBar.include({
@ -16,8 +16,7 @@
self.$('button[data-action=edit]')
.text("Translate")
.after(openerp.qweb.render('website.TranslatorAdditionalButtons'));
self.$('[data-action=snippet]').hide();
self.$('#customize-menu-button').hide();
self.$('.js_hide_on_translate').hide();
});
},
edit: function () {

View File

@ -24,6 +24,7 @@
<div class="modal-body"><t t-raw="__content__"/></div>
<div class="modal-footer">
<button type="button" class="btn" data-dismiss="modal" aria-hidden="true">Discard</button>
<button type="button" class="btn hidden wait" disabled="disabled"/>
<button type="button" class="btn btn-primary save">Save Changes</button>
</div>
</div>
@ -39,22 +40,22 @@
<form>
<ul class="list-group">
<li class="list-group-item form-group active">
<h3 class="list-group-item-heading">
<h4 class="list-group-item-heading">
<label for="link-existing" class="control-label">
Existing page
</label>
</h3>
</h4>
<select class="existing form-control url-source"
id="link-existing">
<option/>
</select>
</li>
<li class="list-group-item form-group">
<h3 class="list-group-item-heading">
<h4 class="list-group-item-heading">
<label for="link-new" class="control-label">
New page
</label>
</h3>
</h4>
<input type="text" class="form-control pages url-source"
id="link-new" placeholder="Page Name"/>
</li>
@ -65,20 +66,20 @@
Open in new window
</label>
</div>
<h3 class="list-group-item-heading">
<h4 class="list-group-item-heading">
<label for="link-external" class="control-label">
External Website
URL
</label>
</h3>
</h4>
<input type="text" class="form-control url url-source"
id="link-external" placeholder="http://openerp.com"/>
</li>
<li class="list-group-item form-group">
<h3 class="list-group-item-heading">
<h4 class="list-group-item-heading">
<label for="link-email" class="control-label">
Email Address
</label>
</h3>
</h4>
<input type="email" class="form-control email-address url-source"
id="link-email" placeholder="you@yourwebsite.com"/>
</li>

View File

@ -0,0 +1,59 @@
<templates id="template" xml:space="preserve">
<t t-name="website.menu.dialog.submenu">
<li t-att-data-menu-id="submenu.id">
<div>
<i class="icon-reorder"/>
<span><t t-esc="submenu.name"/></span>
<div class="btn-group btn-group-xs pull-right oe_menu_buttons">
<button type="button" class="btn js_edit_menu">
<i class="icon-edit"/>
</button>
<button type="button" class="btn js_delete_menu">
<i class="icon-trash"/>
</button>
</div>
</div>
<t t-set="children" t-value="submenu.children"/>
<ul t-if="children">
<t t-foreach="children" t-as="submenu">
<t t-call="website.menu.dialog.submenu"/>
</t>
</ul>
</li>
</t>
<t t-name="website.menu.dialog.footer-button">
<button type="button" class="btn pull-left js_add_menu btn-success">
<i class="icon-plus-sign"/> Add Menu Entry
</button>
</t>
<t t-name="website.menu.dialog.edit">
<t t-call="website.editor.dialog">
<t t-set="title">Edit Structure</t>
<ul class="oe_menu_editor">
<t t-foreach="widget.menu.children" t-as="submenu">
<t t-call="website.menu.dialog.submenu"/>
</t>
</ul>
</t>
</t>
<t t-name="website.menu.dialog.add" t-extend="website.editor.dialog.link">
<t t-jquery="t[t-set='title']" t-operation="inner">
<t t-if="!widget.update_mode">Add Menu Entry</t>
<t t-if="widget.update_mode">Edit Menu Entry</t>
</t>
<t t-jquery="form > div.form-horizontal" t-operation="replace"/>
<t t-jquery="ul.list-group" t-operation="before">
<div class="list-group">
<div class="form-group list-group-item active">
<h3 class="list-group-item-heading">
<label for="link-new" class="control-label">
Menu Label
</label>
</h3>
<input type="text" class="form-control"
id="link-text" required="required"/>
</div>
</div>
</t>
</t>
</templates>

View File

@ -32,7 +32,7 @@
<!-- filled in JS -->
</section>
<section>
<h3 class="page-header">2. Reference Your Page <small>using above suggestions</small></h3>
<h3 class="page-header">2. Reference Your Page <small>using above suggested keywords</small></h3>
<div class="form-horizontal mt16" role="form">
<div class="form-group">
<label for="seo_page_title" class="col-lg-2 control-label">Title</label>

View File

@ -3,7 +3,7 @@
<!-- Snippet loader -->
<t t-name="website.snippets_button">
<li class="navbar-form"><button type="button" data-action="snippet" class="btn btn-primary">Insert Blocks</button></li>
<li class="navbar-form js_hide_on_translate"><button type="button" data-action="snippet" class="hidden btn btn-primary">Insert Blocks</button></li>
</t>
<t t-name="website.snippets_style">
<li class="navbar-form">
@ -63,7 +63,7 @@
<div class="btn-group">
<a href="#" class="btn btn-default btn-sm oe_snippet_parent" title="Select Container Block"><span class="icon-upload-alt"/></a>
<div class="dropdown oe_options hidden btn-group">
<a href="#" data-toggle="dropdown" class="btn btn-default btn-sm" title="Customize">Customize <span class="caret"/></a>
<a href="#" data-toggle="dropdown" class="btn btn-primary btn-sm" title="Customize">Customize <span class="caret"/></a>
<ul class="dropdown-menu" role="menu"></ul>
</div>
<a href="#" class="btn btn-default btn-sm oe_snippet_move" title="Drag to Move">&amp;nbsp; <span class="icon-move"/> &amp;nbsp;</a>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<templates id="template" xml:space="preserve">
<t t-name="website.TranslatorAdditionalButtons">
<strong class="oe_translate_or">or</strong>
<a class="btn btn-default" data-action="edit_master" href="#">Edit Master</a>
<span class="oe_translate_or">or</span>
<a class="btn btn-link" data-action="edit_master" href="#">Edit Master</a>
</t>
<t t-name="website.TranslatorDialog">
<div class="modal fade oe_website_translator" tabindex="-1" role="dialog">
@ -18,22 +18,23 @@
You are about to enter the translation mode.
</p>
<p>
In this mode you can not change the structure of the document, you can only translate text.
</p>
<p>
Here are the visuals used in order to help you in your translation flow:
Here are the visuals used to help you translate efficiently:
<ul class="oe_translate_examples">
<li class="oe_translatable_text">
Normal translatable content
</li>
<li class="oe_translatable_field">
ERP translatable object data
</li>
<li class="oe_translatable_todo">
Translation TODO
Content to translate
</li>
<li class="oe_translatable_text">
Translated content
</li>
</ul>
</p>
<p>
In this mode, you can only translate texts. To
change the structure of the page, you must edit the
master page. Each modification on the master page
is automatically applied to all translated
versions.
</p>
</section>
<hr/>
<section class="row">

View File

@ -22,7 +22,19 @@
<li><a data-action="show-mobile-preview" href="#"><span title="Mobile preview" class="icon-mobile-phone"/></a></li>
<li class="divider-vertical"></li>
<li><a data-action="promote-current-page" href="#"><span title="Promote page on the web">Promote</span></a></li>
<li class="dropdown">
<li class="dropdown js_hide_on_translate">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">Content <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a data-action="edit-structure" href="#"><span title="Edit Top Menu">Edit Menu</span></a></li>
<li class="divider"> </li>
<li><a href="">New Page</a></li> <!-- Todo: Implement with inheritancy in each module-->
<li><a href="">New Blog Post</a></li>
<li><a href="">New Event</a></li>
<li><a href="">New Job Offer</a></li>
<li><a href="">New Product</a></li>
</ul>
</li>
<li class="dropdown js_hide_on_translate">
<a id="customize-menu-button" class="dropdown-toggle" data-toggle="dropdown" href="#">Customize <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu" id="customize-menu">
<!-- filled in JS -->
@ -34,9 +46,6 @@
<!-- filled in JS -->
</ul>
</li>
<li>
<a href="/web#action=website.action_module_website">Apps</a>
</li>
</ul>
</div>
</div>
@ -61,4 +70,27 @@
</div>
</t>
<t t-name="website.error_dialog">
<div class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button title="Close" type="button" class="close" data-dismiss="modal">×</button>
<div class="h2"><t t-esc="title"/></div>
</div>
<div class="modal-body">
<section>
<t t-esc="message"/>
</section>
<section class="mt32">
You have encountered an error. You can edit in the <a t-att-href="backend_url">backend</a> this data.
</section>
</div>
<div class="modal-footer">
<button type="button" data-dismiss="modal" href="#" class="close btn btn-default">Close</button>
</div>
</div>
</div>
</div>
</t>
</templates>

View File

@ -1,4 +1,4 @@
# -*- coding: utf-8 -*-
import test_views, test_converter
import test_views, test_converter, test_requests
checks = [ test_views, test_converter, ]

View File

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
import unittest2
class URLCase(unittest2.TestCase):
"""
URLCase moved out of test_requests, otherwise discovery attempts to
instantiate and run it
"""
def __init__(self, user, url, result):
super(URLCase, self).__init__()
self.user = user
self.url = url
self.result = result
@property
def username(self):
return self.user or "Anonymous Coward"
def __str__(self):
return "%s (as %s)" % (self.url, self.username)
__repr__ = __str__
def shortDescription(self):
return ""
def runTest(self):
code = self.result.getcode()
self.assertIn(
code, xrange(200, 300),
"Fetching %s as %s returned an error response (%d)" % (
self.url, self.username, code))

View File

@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
import collections
import urllib
import urlparse
import unittest2
import urllib2
import lxml.html
from openerp import tools
from . import cases
__all__ = ['load_tests', 'CrawlSuite']
class RedirectHandler(urllib2.HTTPRedirectHandler):
"""
HTTPRedirectHandler is predicated upon HTTPErrorProcessor being used and
works by intercepting 3xy "errors".
Inherit from it to handle 3xy non-error responses instead, as we're not
using the error processor
"""
def http_response(self, request, response):
code, msg, hdrs = response.code, response.msg, response.info()
if 300 <= code < 400:
return self.parent.error(
'http', request, response, code, msg, hdrs)
return response
https_response = http_response
class CrawlSuite(unittest2.TestSuite):
""" Test suite crawling an openerp CMS instance and checking that all
internal links lead to a 200 response.
If a username and a password are provided, authenticates the user before
starting the crawl
"""
def __init__(self, user=None, password=None):
super(CrawlSuite, self).__init__()
self.opener = urllib2.OpenerDirector()
self.opener.add_handler(urllib2.UnknownHandler())
self.opener.add_handler(urllib2.HTTPHandler())
self.opener.add_handler(urllib2.HTTPSHandler())
self.opener.add_handler(RedirectHandler())
self.opener.add_handler(urllib2.HTTPCookieProcessor())
self._authenticate(user, password)
self.user = user
def _request(self, path):
return self.opener.open(urlparse.urlunsplit([
'http', 'localhost:%s' % tools.config['xmlrpc_port'],
path, '', ''
]))
def _authenticate(self, user, password):
# force tools.config['db_name'] in user session so opening `/` doesn't
# blow up in multidb situations
self.opener.open('http://localhost:{port}/web/?db={db}'.format(
port=tools.config['xmlrpc_port'],
db=urllib.quote_plus(tools.config['db_name']),
))
if user is not None:
url = 'http://localhost:{port}/login?{query}'.format(
port=tools.config['xmlrpc_port'],
query=urllib.urlencode({
'db': tools.config['db_name'],
'login': user,
'key': password,
})
)
auth = self.opener.open(url)
assert auth.getcode() < 400, "Auth failure %d" % auth.getcode()
def _wrapped_run(self, result, debug=False):
paths = collections.deque(['/'])
seen = set(paths)
while paths:
url = paths.popleft()
r = self._request(url)
cases.URLCase(self.user, url, r).run(result)
if r.info().gettype() != 'text/html':
continue
doc = lxml.html.fromstring(r.read())
for link in doc.xpath('//a[@href]'):
href = link.get('href')
# avoid repeats, even for links we won't crawl no need to
# bother splitting them if we've already ignored them
# previously
if href in seen: continue
seen.add(href)
parts = urlparse.urlsplit(href)
if parts.netloc or \
not parts.path.startswith('/') or \
parts.path == '/web' or\
parts.path.startswith('/web/') or \
(parts.scheme and parts.scheme not in ('http', 'https')):
continue
paths.append(href)
def load_tests(loader, base, _):
base.addTest(CrawlSuite())
# blog duplicate (&al?) are on links
base.addTest(CrawlSuite('admin', tools.config['admin_passwd']))
base.addTest(CrawlSuite('demo', 'demo'))
return base

View File

@ -62,7 +62,7 @@
<img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_banner.png"/>
<span class="oe_snippet_thumbnail_title">Banner</span>
</div>
<div id="myCarousel" class="oe_snippet_body carousel slide oe_medium mb32" contenteditable="false">
<div id="myCarousel" class="oe_snippet_body carousel slide oe_medium mb32">
<!-- Indicators -->
<ol class="carousel-indicators hidden">
<li data-target="#myCarousel" data-slide-to="0" class="active"></li>
@ -70,7 +70,7 @@
<div class="carousel-inner">
<div class="item image_text active" style="background-image: url('/website/static/src/img/banner/color_splash.jpg')">
<div class="container">
<div class="carousel-caption content" contenteditable="true">
<div class="carousel-caption content">
<h2>Your Banner Title</h2>
<h3>Click to customize this text</h3>
<p>
@ -78,7 +78,7 @@
</p>
</div>
<div class="carousel-image hidden-xs">
<img src="/website/static/src/img/banner/banner_picture.png" contenteditable="true" alt="Banner OpenERP Image"/>
<img src="/website/static/src/img/banner/banner_picture.png" alt="Banner OpenERP Image"/>
</div>
</div>
</div>
@ -109,7 +109,7 @@
</p>
</div>
<div class="col-md-6 mt16 mb16">
<img class="img-responsive shadow" src="/website/static/src/img/text_image.png"/>
<img class="img img-responsive shadow" src="/website/static/src/img/text_image.png"/>
</div>
</div>
</div>
@ -126,7 +126,7 @@
<div class="container">
<div class="row">
<div class="col-md-6 mt16 mb16">
<img class="img-responsive shadow" src="/website/static/src/img/image_text.jpg"/>
<img class="img img-responsive shadow" src="/website/static/src/img/image_text.jpg"/>
</div>
<div class="col-md-6 mt32">
<h3>A Section Subtitle</h3>
@ -243,7 +243,7 @@
<h2>A Punchy Headline</h2>
</div>
<div class="col-md-12">
<img class="img-responsive" src="/website/static/src/img/big_picture.png" style="margin: 0 auto;"/>
<img class="img img-responsive" src="/website/static/src/img/big_picture.png" style="margin: 0 auto;"/>
</div>
<div class="col-md-6 col-md-offset-3 mb16 mt16">
<p class="text-center">
@ -277,7 +277,7 @@
<h3 class="text-muted">And a good subtitle</h3>
</div>
<div class="col-md-4">
<img class="img-rounded img-responsive" src="/website/static/src/img/china_thumb.jpg"/>
<img class="img img-rounded img-responsive" src="/website/static/src/img/china_thumb.jpg"/>
<h4 class="mt16">Streamline Recruitments</h4>
<p>
Post job offers and keep track of each application
@ -290,7 +290,7 @@
</p>
</div>
<div class="col-md-4">
<img class="img-rounded img-responsive" src="/website/static/src/img/desert_thumb.jpg"/>
<img class="img img-rounded img-responsive" src="/website/static/src/img/desert_thumb.jpg"/>
<h4 class="mt16">Enterprise Social Network</h4>
<p>
Break down information silos. Share knowledge and best
@ -302,7 +302,7 @@
</p>
</div>
<div class="col-md-4">
<img class="img-rounded img-responsive" src="/website/static/src/img/deers_thumb.jpg"/>
<img class="img img-rounded img-responsive" src="/website/static/src/img/deers_thumb.jpg"/>
<h4 class="mt16">Leaves Management</h4>
<p>
Keep track of the vacation days accrued by each
@ -323,6 +323,7 @@
<div data-snippet-id="well" data-selector-siblings="p, h1, h2, h3, blockquote">
<div class="oe_snippet_thumbnail">
<img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_well.png"/>
<span class="oe_snippet_thumbnail_title">Well</span>
</div>
<div class="oe_snippet_body well">
@ -381,19 +382,19 @@
<h4 class="text-muted">More than 500 successful projects</h4>
</div>
<div class="col-md-4">
<img class="img-thumbnail img-responsive" src="/website/static/src/img/deers.jpg"/>
<img class="img-thumbnail img-responsive" src="/website/static/src/img/desert.jpg"/>
<img class="img-thumbnail img-responsive" src="/website/static/src/img/china.jpg"/>
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/deers.jpg"/>
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/desert.jpg"/>
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/china.jpg"/>
</div>
<div class="col-md-4">
<img class="img-thumbnail img-responsive" src="/website/static/src/img/desert.jpg"/>
<img class="img-thumbnail img-responsive" src="/website/static/src/img/china.jpg"/>
<img class="img-thumbnail img-responsive" src="/website/static/src/img/deers.jpg"/>
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/desert.jpg"/>
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/china.jpg"/>
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/deers.jpg"/>
</div>
<div class="col-md-4">
<img class="img-thumbnail img-responsive" src="/website/static/src/img/landscape.jpg"/>
<img class="img-thumbnail img-responsive" src="/website/static/src/img/china.jpg"/>
<img class="img-thumbnail img-responsive" src="/website/static/src/img/desert.jpg"/>
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/landscape.jpg"/>
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/china.jpg"/>
<img class="img img-thumbnail img-responsive" src="/website/static/src/img/desert.jpg"/>
</div>
</div>
</div>
@ -414,28 +415,28 @@
<h4 class="text-muted">More than 500 successful projects</h4>
</div>
<div class="col-md-6">
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/desert.jpg"/>
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/desert.jpg"/>
</div>
<div class="col-md-3">
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/china_thumb.jpg"/>
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/china_thumb.jpg"/>
</div>
<div class="col-md-3">
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/deers_thumb.jpg"/>
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/deers_thumb.jpg"/>
</div>
<div class="col-md-3">
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/desert_thumb.jpg"/>
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/desert_thumb.jpg"/>
</div>
<div class="col-md-3">
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/china_thumb.jpg"/>
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/china_thumb.jpg"/>
</div>
<div class="col-md-3">
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/deers_thumb.jpg"/>
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/deers_thumb.jpg"/>
</div>
<div class="col-md-6">
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/landscape.jpg"/>
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/landscape.jpg"/>
</div>
<div class="col-md-3">
<img class="img-thumbnail img-responsive mb16" src="/website/static/src/img/china_thumb.jpg"/>
<img class="img img-thumbnail img-responsive mb16" src="/website/static/src/img/china_thumb.jpg"/>
</div>
</div>
</div>
@ -554,7 +555,7 @@
<img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_button.png"/>
<span class="oe_snippet_thumbnail_title">Button</span>
</div>
<section class="oe_snippet_body dark">
<section class="oe_snippet_body oe_snippet_darken dark">
<div class="container">
<div class="row">
<div class="col-md-12 text-center mt16 mb16">
@ -649,19 +650,19 @@
</blockquote>
</div>
<div class="col-md-2 col-md-offset-1">
<img src="/website/static/src/img/openerp_logo.png" class="img-responsive img-thumbnail"/>
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail"/>
</div>
<div class="col-md-2">
<img src="/website/static/src/img/openerp_logo.png" class="img-responsive img-thumbnail"/>
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail"/>
</div>
<div class="col-md-2">
<img src="/website/static/src/img/openerp_logo.png" class="img-responsive img-thumbnail"/>
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail"/>
</div>
<div class="col-md-2">
<img src="/website/static/src/img/openerp_logo.png" class="img-responsive img-thumbnail"/>
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail"/>
</div>
<div class="col-md-2">
<img src="/website/static/src/img/openerp_logo.png" class="img-responsive img-thumbnail"/>
<img src="/website/static/src/img/openerp_logo.png" class="img img-responsive img-thumbnail"/>
</div>
</div>
</div>
@ -690,9 +691,9 @@
<ul class="dropdown-menu" name="parallax-background">
<li data-value="/website/static/src/img/banner/greenfields.jpg"><a>Greenfields</a></li>
<li data-value="/website/static/src/img/banner/landscape.jpg"><a>Landscape</a></li>
<li data-value="/website/static/src/img/parallax/parallax_photo1.jpg"><a>Photo Woman</a></li>
<li data-value="/website/static/src/img/banner/mountains.jpg"><a>Mountains</a></li>
<li class="oe_custom_bg"><a><b>Chose your picture</b></a></li>
<li data-value="/website/static/src/img/parallax/parallax_bg.jpg"><a>Office</a></li>
<li class="oe_custom_bg"><a><b>Choose your picture</b></a></li>
</ul>
</li>
<li class="oe_snippet_options dropdown-submenu">
@ -706,16 +707,15 @@
<li data-value="2"><a>Very Fast</a></li>
</ul>
</li>
<div class="oe_snippet_body" style="position:relative">
<div class="parallax oe_structure oe_small" style="background-image: url('/website/static/src/img/parallax/parallax_photo1.jpg')" data-stellar-background-ratio="0.3">
</div>
<div class="oe_snippet_body parallax oe_small oe_structure"
style="background-image: url('/website/static/src/img/parallax/parallax_bg.jpg')"
data-scroll-background-ratio="0.6">
</div>
</div>
<div data-snippet-id="parallax_quote" data-selector-children=".oe_structure, [data-oe-type=html]">
<div class="oe_snippet_thumbnail">
<img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_parallax.png"/>
<img class="oe_snippet_thumbnail_img" src="/website/static/src/img/blocks/block_quotes_slider.png"/>
<span class="oe_snippet_thumbnail_title">Quotes Slider</span>
</div>
<section class="oe_snippet_body parallax_quote oe_structure">

View File

@ -10,9 +10,27 @@
<link id="website_css" rel='stylesheet' href='/website/static/src/css/website.css' t-ignore="true"/>
</template>
<template id="layout" name="Main layout">
&lt;!DOCTYPE html&gt;
<template id="website.submenu" name="Submenu">
<li t-if="not submenu.child_id">
<a t-att-href="url_for(submenu.url)" t-ignore="true">
<span t-field="submenu.name"/>
</a>
</li>
<li t-if="submenu.child_id" class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<span t-field="submenu.name"/> <span class="caret" t-ignore="true"></span>
</a>
<ul class="dropdown-menu" role="menu">
<t t-foreach="submenu.child_id" t-as="submenu">
<t t-call="website.submenu"/>
</t>
</ul>
</li>
</template>
<template id="layout" name="Main layout">&lt;!DOCTYPE html&gt;
<html t-att-lang="lang.replace('_', '-')"
t-att-data-website-id="website.id if editable else None"
t-att-data-editable="'1' if editable else None"
t-att-data-translatable="'1' if translatable else None"
t-att-data-view-xmlid="xmlid if editable else None"
@ -49,11 +67,15 @@
<script type="text/javascript" src="/web/static/lib/underscore.string/lib/underscore.string.js"></script>
<script type="text/javascript" src="/web/static/lib/jquery/jquery.js"></script>
<script type="text/javascript" src="/website/static/lib/bootstrap/js/bootstrap.js"></script>
<script type="text/javascript">
// Bootstrap and jQuery UI conflicts
$.fn.bstooltip = $.fn.tooltip;
$.fn.bsbutton = $.fn.button;
</script>
<script type="text/javascript" src="/web/static/lib/qweb/qweb2.js"></script>
<script type="text/javascript" src="/web/static/src/js/openerpframework.js"></script>
<script type="text/javascript" src="/website/static/lib/stellar/jquery.stellar.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.js"></script>
<t t-if="editable">
@ -65,11 +87,13 @@
<script type="text/javascript" src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script>
<link rel='stylesheet' href="/web/static/lib/jquery.ui/css/smoothness/jquery-ui-1.9.1.custom.css"/>
<!-- mutation observers shim backed by mutation events (8 < IE < 11, Safari < 6, FF < 14, Chrome < 17) -->
<script type="text/javascript" src="/website/static/lib//jquery.mjs.nestedSortable/jquery.mjs.nestedSortable.js"></script>
<script type="text/javascript" src="/website/static/lib/MutationObservers/test/sidetable.js"></script>
<script type="text/javascript" src='/website/static/lib/nearest/jquery.nearest.js'></script>
<script type="text/javascript" src="/website/static/lib/MutationObservers/MutationObserver.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.editor.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.menu.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.mobile.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.seo.js"></script>
<script type="text/javascript" src="/website/static/src/js/website.tour.js"></script>
@ -93,38 +117,22 @@
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" t-href="/page/website.homepage"><em>Your</em><b>Company</b></a>
<a class="navbar-brand" href="/">Your<b>Company</b></a>
</div>
<div class="collapse navbar-collapse navbar-top-collapse">
<ul class="nav navbar-nav navbar-right" id="top_menu">
<li name="contactus"><a t-href="/page/website.contactus">Contact us</a></li>
<li t-if="user_id.id == website.public_user.id"><a href="/web">Sign in</a></li>
<li t-if="user_id.id != website.public_user.id"><a href="/web"><span t-field="user_id.name"/></a></li>
<li t-if="request.multilang and
(len(website.language_ids) &gt; 1 or editable)" class="dropdown">
<!-- TODO: use flags for language selection -->
<a class="dropdown-toggle js_language_selected" data-toggle="dropdown" href="#">
<t t-esc="lang_selected[0]['name'].split('/').pop()"/> <span class="caret"></span>
<t t-foreach="website.get_menu().child_id" t-as="submenu">
<t t-call="website.submenu"/>
</t>
<li class="active dropdown" t-ignore="true" t-if="user_id.id != website.public_user.id">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<span t-esc="user_id.name"/>
<span class="caret"></span>
</a>
<ul class="dropdown-menu js_language_selector" role="menu">
<li t-foreach="website.language_ids" t-as="lg">
<a t-att-href="url_for('', lang=lg.code)" role="menuitem"
t-att-data-default-lang="editable and 'true' if lg.code == website.default_lang_id.code else None">
<strong t-att-class="'icon-circle' if lg.code == lang
else 'icon-circle-blank'"></strong>
<t t-esc="lg.name.split('/').pop()"/>
</a>
</li>
<li t-if="editable" class="divider"/>
<li t-if="editable">
<t t-set="url_return" t-value="request.multilang and url_for(request.httprequest.path, '[lang]') or request.httprequest.path"/>
<t t-if="request.httprequest.query_string">
<t t-set="url_return" t-value="url_return + '?' + request.httprequest.query_string"/>
</t>
<a t-attf-href="/web#action=base.action_view_base_language_install&amp;website_id=#{website.id}&amp;url_return=#{url_return}">
Add a language...
</a>
</li>
<ul class="dropdown-menu js_usermenu" role="menu">
<li><a href="/web" role="menuitem">Administration</a></li>
<li class="divider"/>
<li><a t-attf-href="/web/session/logout?redirect=#{ url_for('') }" role="menuitem">Logout</a></li>
</ul>
</li>
</ul>
@ -161,24 +169,42 @@
</h2>
</div>
<div class="col-md-5 col-lg-offset-1" name="about_us">
<h4>
<span t-field="res_company.name">Your Company</span>
<small> - <a href="/page/website.aboutus">About us</a></small>
</h4>
<p>
We are a team of passionated people whose goal is to improve everyone's
life through disruptive products. We build great products to solve your
business problems.
</p>
<p>
Our products are designed for small to medium companies willing to optimize
their performance.
</p>
<div>
<h4>
<span t-field="res_company.name">Your Company</span>
<small> - <a href="/page/website.aboutus">About us</a></small>
</h4>
<p>
We are a team of passionated people whose goal is to improve everyone's
life through disruptive products. We build great products to solve your
business problems.
</p>
<p>
Our products are designed for small to medium companies willing to optimize
their performance.
</p>
</div>
<ul class="nav nav-pills js_language_selector" t-if="request.multilang and
(len(website.language_ids) &gt; 1 or editable)">
<li t-foreach="website.language_ids" t-as="lg">
<a t-att-href="url_for('', lang=lg.code)"
t-att-data-default-lang="editable and 'true' if lg.code == website.default_lang_id.code else None">
<t t-esc="lg.name.split('/').pop()"/>
</a>
</li>
<li t-if="editable">
<t t-set="url_return" t-value="request.multilang and url_for(request.httprequest.path, '[lang]') or request.httprequest.path"/>
<a t-attf-href="/web#action=base.action_view_base_language_install&amp;website_id=#{website.id}&amp;url_return=#{url_return}">
<i class="icon-plus-sign"/>
Add a language...
</a>
</li>
</ul>
</div>
</div>
</div>
<div class="container mt16">
<div class="pull-right" t-ignore="1">
<div class="pull-right" t-ignore="true" t-if="not editable">
Create a <a href="http://openerp.com/apps/website">free website</a> with
<a class="label label-danger" href="https://openerp.com/apps/website">OpenERP</a>
</div>
@ -192,32 +218,44 @@
</html>
</template>
<template id="show_sign_in" inherit_option_id="website.layout" inherit_id="website.layout" name="Show Sign In">
<xpath expr="//ul[@id='top_menu']" position="inside">
<li class="active" t-if="user_id.id == website.public_user.id">
<a t-attf-href="/web#redirect=#{ url_for('') }">
Sign in
</a>
</li>
</xpath>
</template>
<template id="footer_custom" inherit_option_id="website.layout" name="Custom Footer">
<xpath expr="//div[@id='footer_container']" position="before">
<section data-snippet-id='three-columns' class="mt16 mb16">
<div class="container">
<div class="row">
<div class="col-md-4">
<h4 class="mt16">Subtitle</h4>
<p>
<a t-href="/">Homepage</a>
</p>
</div>
<div class="col-md-4">
<h4 class="mt16">Subtitle 2</h4>
<p>
...
</p>
</div>
<div class="col-md-4">
<h4 class="mt16">Subtitle 3</h4>
<p>
...
</p>
<div class="oe_structure">
<section data-snippet-id='three-columns' class="mt16 mb16">
<div class="container">
<div class="row">
<div class="col-md-4">
<h4 class="mt16">Subtitle</h4>
<p>
<a href="/">Homepage</a>
</p>
</div>
<div class="col-md-4">
<h4 class="mt16">Subtitle 2</h4>
<p>
...
</p>
</div>
<div class="col-md-4">
<h4 class="mt16">Subtitle 3</h4>
<p>
...
</p>
</div>
</div>
</div>
</div>
</section>
</section>
</div>
</xpath>
<xpath expr="//div[@id='footer_container']" position="attributes">
<attribute name="style">display: none</attribute>
@ -225,9 +263,8 @@
</template>
<template id="publish_management">
<t t-if="editable" t-ignore="true">
<div class="pull-right">
<div t-attf-class="btn-group dropdown js_publish_management #{object.id and object.website_published and 'css_publish' or 'css_unpublish'}" t-att-data-id="object.id" t-att-data-object="object._name">
<div t-if="editable" t-ignore="true" class="pull-right hidden-xs" t-att-style="style or ''">
<div t-attf-class="btn-group dropdown js_publish_management #{object.id and object.website_published and 'css_publish' or 'css_unpublish'}" t-att-data-id="object.id" t-att-data-object="object._name" t-att-data-controller="publish_controller">
<a t-attf-class="btn btn-sm btn-#{object.id and object.website_published and 'success' or 'default'}" t-att-id="'dopprod-%s' % object.id" role="button" data-toggle="dropdown">Options <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu" t-att-aria-labelledby="'dopprod-%s' % object.id">
<t t-raw="0"/>
@ -235,9 +272,6 @@
<a href="#" class="js_publish_btn css_unpublish">Unpublish</a>
<a href="#" class="js_publish_btn css_publish">Publish</a>
</li>
<li t-if="publish_duplicate">
<a t-att-href="publish_duplicate">Duplicate</a>
</li>
<li t-if="publish_edit">
<a t-att-href="'/web#model=%s&amp;id=%s' % (object._name, object.id)"
title='Edit in backend'>Edit</a>
@ -245,7 +279,6 @@
</ul>
</div>
</div>
</t>
</template>
<template id="publish_short">
@ -262,7 +295,7 @@
</template>
<template id="pager" name="Pager">
<ul t-if="pager['page_count'] > 1" t-attf-class="#{ classname or '' } pagination">
<ul t-if="pager['page_count'] > 1" t-attf-class="#{ classname or '' } pagination" t-att-style="style or ''">
<li t-att-class=" 'disabled' if pager['page']['num'] == 1 else '' ">
<a t-att-href=" pager['page_previous']['url'] if pager['page']['num'] != 1 else '' ">Prev</a>
</li>
@ -364,8 +397,8 @@
<h1 class="container mt32">500: Internal Server Error!</h1>
</div>
<t t-if="editable">
<h3>An exception appear in the template: <span id="exception_template" t-esc="template"/></h3>
<b id="exception_message" t-esc="message"/>
<h3>Exception in template: <span id="exception_template" t-esc="template"/></h3>
<h4 t-if="expr">Expression: <t t-esc="expr"/></h4>
<pre id="exception_node" t-esc="node"/>
<pre id="exception_traceback" t-esc="traceback"/>
</t>
@ -401,10 +434,22 @@ User-agent: *
Sitemap: <t t-esc="url_root"/>sitemap.xml
</template>
<template id="sitemap">
<template id="sitemap" name="Site Map">
<t t-call="website.layout">
<ul>
<li t-foreach="pages" t-as="page">
<a t-att-href="page['url']"><t t-esc="page['name']"/></a>
</li>
</ul>
</t>
</template>
<template id="sitemap_xml">
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<t t-foreach="pages" t-as="page">
<url t-esc="page['url']"/>
<url>
<loc><t t-esc="page['url']"/></loc>
</url>
</t>
</urlset>
</template>
@ -427,8 +472,8 @@ Sitemap: <t t-esc="url_root"/>sitemap.xml
<span>&amp;#x2706; <span t-field="res_company.phone"></span></span><br />
<i class="icon-envelope"></i> <span t-field="res_company.email"></span>
</address>
<a t-att-href="res_company.partner_id.google_map_link()" target="_BLANK">
<img class="thumbnail img-responsive" t-att-src="res_company.partner_id.google_map_img()" />
<a t-att-href="res_company.google_map_link()" target="_BLANK">
<img class="thumbnail img-responsive" t-att-src="res_company.google_map_img()" />
</a>
</template>
@ -460,31 +505,65 @@ Sitemap: <t t-esc="url_root"/>sitemap.xml
<template id="aboutus" name="About us" page="True">
<t t-call="website.layout">
<div id="wrap">
<div class="oe_structure"/>
<div class="container mb32">
<div class="row col-wrap" id="aboutus">
<div class="col-sm-12 text-center">
<h1>About us</h1>
<h3 class="text-muted">A passion for great products</h3>
</div>
<div class="col-sm-8 mt16">
<p>
We are a team of passionated people whose goal is to improve everyone's
life through disruptive products. We build great products to solve your
business problems.
</p>
<p>
Our products are designed for small to medium companies willing to optimize
their performance.
</p>
</div><div class="col-sm-3 col-sm-offset-1">
<img src="/website/static/src/img/library/business_conference.jpg" class="img img-responsive shadow" alt="Out Team"/>
</div>
</div>
</div>
<div class="oe_structure"/>
</div>
<div id="wrap" class="oe_structure">
<section data-snippet-id="title">
<div class="container">
<div class="row">
<div class="col-md-12">
<h1 class="text-center">About us</h1>
<h3 class="text-muted text-center">Great products for great people</h3>
</div>
</div>
</div>
</section>
<section data-snippet-id="text-image">
<div class="container">
<div class="row">
<div class="col-md-6 mt32">
<p>
We are a team of passionated people whose goal is to improve everyone's
life through disruptive products. We build great products to solve your
business problems.
</p>
<p>
Our products are designed for small to medium companies willing to optimize
their performance.
</p>
</div>
<div class="col-md-4 col-md-offset-2 mt16 mb16">
<img src="/website/static/src/img/library/business_conference.jpg" class="img img-responsive shadow" alt="Our Team"/>
</div>
</div>
</div>
</section>
<div class="parallax oe_structure mt16 oe_medium mb64" data-scroll-background-offset="0" data-scroll-background-ratio="1" data-snippet-id="parallax" style="background-image: url(http://localhost:8069/website/static/src/img/parallax/parallax_bg.jpg); background-attachment: scroll; background-position: 0px 0px; ">
<section class="mb32 mt16" data-snippet-id="references">
<div class="container">
<div class="row">
<div class="col-md-12 mt16 mb8">
<h1 class="text-center">What do customers say about us...</h1>
</div>
<div class="col-md-4 col-md-offset-1 mt16 mb0">
<blockquote data-snippet-id="quote">
<p><span style="background-color:#FFFFFF;">Write here a quote from one of your customer. Quotes are are great way to give confidence in your products or services.</span></p>
<small><span style="background-color:#FFFFFF;">Author of this quote</span></small>
</blockquote>
</div>
<div class="col-md-4 col-md-offset-2 mt16 mb32">
<blockquote data-snippet-id="quote">
<p><span style="background-color:#FFFFFF;">OpenERP provides essential platform for our project management. Things are better organized and more visible with it.</span></p>
<small><span style="background-color:#FFFFFF;">John Doe, CEO</span></small>
</blockquote>
</div>
</div>
</div>
</section>
</div>
</div>
</t>
</template>

View File

@ -45,9 +45,6 @@
</div>
<group>
<group string="Social Icons">
<p class="oe_grey" colspan="2">
Keep these fields empty to not show the related social icon.
</p>
<field name="social_twitter" placeholder="https://twitter.com/openerp"/>
<field name="social_facebook" placeholder="https://facebook.com/openerp"/>
<field name="social_googleplus" placeholder="https://plus.google.com/+openerp"/>

View File

@ -35,24 +35,23 @@ class WebsiteBlog(http.Controller):
@website.route([
'/blog/',
'/blog/<int:blog_post_id>/',
'/blog/<int:blog_post_id>/page/<int:page>/',
'/blog/cat/<int:category_id>/',
'/blog/cat/<int:category_id>/page/<int:page>/',
'/blog/tag/',
'/blog/tag/<int:tag_id>/',
], type='http', auth="public")
def blog(self, category_id=None, blog_post_id=None, tag_id=None, page=1, **post):
'/blog/page/<int:page>/',
'/blog/<model("blog.post"):blog_post>/',
'/blog/<model("blog.post"):blog_post>/page/<int:page>/',
'/blog/cat/<model("blog.category"):category>/',
'/blog/cat/<model("blog.category"):category>/page/<int:page>/',
'/blog/tag/<model("blog.tag"):tag>/',
'/blog/tag/<model("blog.tag"):tag>/page/<int:page>/',
], type='http', auth="public", multilang=True)
def blog(self, category=None, blog_post=None, tag=None, page=1, enable_editor=None):
""" Prepare all values to display the blog.
:param integer category_id: id of the category currently browsed.
:param integer tag_id: id of the tag that is currently used to filter
blog posts
:param integer blog_post_id: ID of the blog post currently browsed. If not
set, the user is browsing the category and
a post pager is calculated. If set the user
is reading the blog post and a comments pager
is calculated.
:param category: category currently browsed.
:param tag: tag that is currently used to filter blog posts
:param blog_post: blog post currently browsed. If not set, the user is
browsing the category and a post pager is calculated.
If set the user is reading the blog post and a
comments pager is calculated.
:param integer page: current page of the pager. Can be the category or
post pager.
:param dict post: kwargs, may contain
@ -73,44 +72,39 @@ class WebsiteBlog(http.Controller):
"""
cr, uid, context = request.cr, request.uid, request.context
blog_post_obj = request.registry['blog.post']
tag_obj = request.registry['blog.tag']
category_obj = request.registry['blog.category']
tag = None
category = None
blog_post = None
blog_posts = None
pager = None
nav = {}
category_ids = category_obj.search(cr, uid, [], context=context)
categories = category_obj.browse(cr, uid, category_ids, context=context)
if tag_id:
tag = tag_obj.browse(cr, uid, tag_id, context=context)
if category_id:
category = category_obj.browse(cr, uid, category_id, context=context)
elif blog_post_id:
blog_post = blog_post_obj.browse(cr, uid, blog_post_id, context=context)
blog_message_ids = blog_post.website_message_ids
if blog_post:
category = blog_post.category_id
category_id = category.id
if not blog_post_id:
if category and tag:
blog_posts = [cat_post for cat_post in category.blog_post_ids
if tag_id in [post_tag.id for post_tag in cat_post.tag_ids]]
elif category:
pager = request.website.pager(
url="/blog/%s/" % blog_post.id,
total=len(blog_post.website_message_ids),
page=page,
step=self._post_comment_per_page,
scope=7
)
pager_begin = (page - 1) * self._post_comment_per_page
pager_end = page * self._post_comment_per_page
blog_post.website_message_ids = blog_post.website_message_ids[pager_begin:pager_end]
else:
if category:
pager_url = "/blog/cat/%s/" % category.id
blog_posts = category.blog_post_ids
elif tag:
pager_url = '/blog/tag/%s/' % tag.id
blog_posts = tag.blog_post_ids
else:
pager_url = '/blog/'
blog_post_ids = blog_post_obj.search(cr, uid, [], context=context)
blog_posts = blog_post_obj.browse(cr, uid, blog_post_ids, context=context)
if blog_posts:
pager = request.website.pager(
url="/blog/cat/%s/" % category_id,
url=pager_url,
total=len(blog_posts),
page=page,
step=self._category_post_per_page,
@ -120,19 +114,9 @@ class WebsiteBlog(http.Controller):
pager_end = page * self._category_post_per_page
blog_posts = blog_posts[pager_begin:pager_end]
if blog_post:
pager = request.website.pager(
url="/blog/%s/" % blog_post_id,
total=len(blog_message_ids),
page=page,
step=self._post_comment_per_page,
scope=7
)
pager_begin = (page - 1) * self._post_comment_per_page
pager_end = page * self._post_comment_per_page
blog_post.website_message_ids = blog_post.website_message_ids[pager_begin:pager_end]
nav = {}
for group in blog_post_obj.read_group(cr, uid, [], ['name', 'create_date'], groupby="create_date", orderby="create_date asc", context=context):
# FIXME: vietnamese month names contain spaces. Set sail for fail.
year = group['create_date'].split(" ")[1]
if not year in nav:
nav[year] = {'name': year, 'create_date_count': 0, 'months': []}
@ -147,7 +131,7 @@ class WebsiteBlog(http.Controller):
'blog_posts': blog_posts,
'pager': pager,
'nav_list': nav,
'enable_editor': post.get('enable_editor')
'enable_editor': enable_editor,
}
if blog_post:
@ -159,7 +143,9 @@ class WebsiteBlog(http.Controller):
return request.website.render("website_blog.index", values)
@website.route(['/blog/nav'], type='http', auth="public")
# TODO: Refactor (used in website_blog.js for archive links)
# => the archive links should be generated server side
@website.route(['/blog/nav'], type='http', auth="public", multilang=True)
def nav(self, **post):
cr, uid, context = request.cr, request.uid, request.context
blog_post_ids = request.registry['blog.post'].search(
@ -171,32 +157,25 @@ class WebsiteBlog(http.Controller):
blog_post_data = [
{
'id': blog_post.id,
'name': blog_post.name,
'website_published': blog_post.website_published,
'category_id': blog_post.category_id and blog_post.category_id.id or False,
'fragment': request.website.render("website_blog.blog_archive_link", {
'blog_post': blog_post
}),
}
for blog_post in request.registry['blog.post'].browse(cr, uid, blog_post_ids, context=context)
]
return simplejson.dumps(blog_post_data)
@website.route(['/blog/<int:blog_post_id>/post'], type='http', auth="public")
@website.route(['/blog/<int:blog_post_id>/comment'], type='http', auth="public")
def blog_post_comment(self, blog_post_id=None, **post):
cr, uid, context = request.cr, request.uid, request.context
url = request.httprequest.host_url
request.session.body = post.get('body')
if request.context['is_public_user']: # purpose of this ?
return '%s/web#action=redirect&url=%s/blog/%s/post' % (url, url, blog_post_id)
if request.session.get('body') and blog_post_id:
request.registry['blog.post'].message_post(
cr, uid, blog_post_id,
body=request.session.body,
type='comment',
subtype='mt_comment',
context=dict(context, mail_create_nosubcribe=True))
request.session.body = False
return werkzeug.utils.redirect("/blog/%s/?enable_editor=1" % (blog_post_id))
request.registry['blog.post'].message_post(
cr, uid, blog_post_id,
body=post.get('comment'),
type='comment',
subtype='mt_comment',
context=dict(context, mail_create_nosubcribe=True))
return werkzeug.utils.redirect(request.httprequest.referrer + "#comments")
@website.route(['/blog/<int:category_id>/new'], type='http', auth="public")
def blog_post_create(self, category_id=None, **post):

View File

@ -1,6 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<!-- <data noupdate="1"> -->
<data noupdate="1">
<record id="menu_blog" model="website.menu">
<field name="name">News</field>
<field name="url">/blog</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence" type="int">50</field>
</record>
</data>
<data>
<!-- jump to blog at install -->

View File

@ -1,4 +1,4 @@
@import url(compass/css3.css);
@charset "UTF-8";
.css_website_mail .has-error {
border-color: red;
}
@ -22,11 +22,14 @@ p.post-meta {
}
.js_website_blog div#right_column section {
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=60);
opacity: 0.6;
}
.js_website_blog div#right_column section:hover {
filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
opacity: 1;
-moz-transition: all 0.2s ease-out;
-webkit-transition: all 0.2s ease-out;
-moz-transition: all 0.2s ease-out;
-o-transition: all 0.2s ease-out;
transition: all 0.2s ease-out;
}

View File

@ -1,30 +1,29 @@
@charset "utf-8"
@import "compass/css3"
.css_website_mail
.has-error
border-color: red
.css_nav_month
display: none
&:first-of-type
display: block
.has-error
border-color: red
.css_nav_month
display: none
&:first-of-type
display: block
.blog_content
a.oe_mail_expand:after
content: ""
a.oe_mail_expand
font-weight: bold
a.oe_mail_expand:after
content: ""
a.oe_mail_expand
font-weight: bold
p.post-meta
position: relative
top: -5px
position: relative
top: -5px
.js_website_blog
div#right_column
section
opacity: 0.6
section:hover
opacity: 1
-moz-transition: all 0.2s ease-out
-webkit-transition: all 0.2s ease-out
transition: all 0.2s ease-out
div#right_column
section
+opacity(0.6)
section:hover
+opacity(1)
@include transition(all 0.2s ease-out)

View File

@ -8,15 +8,14 @@ $(document).ready(function () {
e.preventDefault();
var $ul = $(this).next("ul");
if (!$ul.find('li').length) {
// TODO: Why POST? (to pass the domain) A GET would be more appropriate...
// This should be done server side anyway...
$.post('/blog/nav', {'domain': $(this).data("domain")}, function (result) {
var blog_id = +window.location.pathname.split("/").pop();
$(JSON.parse(result)).each(function () {
var $a = $('<a href="/blog/' + this.id + '"/>').text(this.name);
var $li = $("<li/>").append($a);
if (blog_id == this.id)
$li.addClass("active");
if (!this.website_published)
$a.css("color", "red");
var $li = $($.parseHTML(this.fragment));
if (blog_id == this.id) $li.addClass("active");
if (!this.website_published) $li.find('a').css("color", "red");
$ul.append($li);
});
@ -26,16 +25,4 @@ $(document).ready(function () {
}
});
var $form = $('.js_website_blog form#comment');
$form.submit(function (e) {
e.preventDefault();
var error = $form.find("textarea").val().length < 3;
$form.find("textarea").toggleClass("has-error", error);
if (!error) {
$form.css("visibility", "hidden");
$.post(window.location.pathname + '/post', {'body': $form.find("textarea").val()}, function (url) {
window.location.href = url
});
}
});
});
});

View File

@ -2,18 +2,9 @@
<openerp>
<data>
<!-- Layout add nav and footer -->
<template id="header_footer_custom" inherit_id="website.layout">
<xpath expr="//header//ul[@id='top_menu']/li[@name='contactus']" position="before">
<li><a t-attf-href="/blog/cat/%(website_blog.blog_category_1)d/">News</a></li>
</xpath>
<template id="header_footer_custom" inherit_id="website.layout" name="Footer News Blog Link">
<xpath expr="//footer//div[@name='info']/ul" position="inside">
<li><a t-attf-href="/blog/cat/%(website_blog.blog_category_1)d/">News</a></li>
</xpath>
</template>
<template id="footer_custom" inherit_option_id="website.layout" name="Job Announces">
<xpath expr="//footer//div[@name='info']/ul" position="inside">
<li><a t-href="/blog/%(website_blog.blog_category_2)d/">Jobs</a></li>
<li><a t-href="/blog/cat/%(website_blog.blog_category_1)d/">News</a></li>
</xpath>
</template>
@ -24,17 +15,24 @@
<t t-call="website.publish_management">
<t t-set="object" t-value="blog_post"/>
<t t-set="publish_edit" t-value="True"/>
<t t-set="publish_duplicate" t-value="'/blog/%s/duplicate' % (blog_post.id)"/>
<li>
<a href="#" t-attf-data-href="/blog/#{blog_post.id}/duplicate">Duplicate</a>
<script>
var $a=$("[data-href$='/duplicate']");
$a.attr("href", $a.data('href')).removeAttr('data-href');
$a.next("script").remove();
</script>
</li>
</t>
</div>
<h2 class="text-center">
<a t-attf-href="/blog/#{blog_post.id}" t-field="blog_post.name"></a>
<a t-href="/blog/#{blog_post.id}" t-field="blog_post.name"></a>
</h2>
<p class="post-meta text-muted text-center" name='blog_post_data'>
<span class="icon-calendar"> <span t-field="blog_post.create_date"/></span> &amp;nbsp;
<span class="icon-user"> By <span t-field="blog_post.create_uid"/> &amp;nbsp;</span>
<span t-if="len(blog_post.message_ids) &gt; 0" class="icon-comment">
<a t-attf-href="/blog/#{blog_post.id}/#comment">
<a t-href="/blog/#{blog_post.id}/#comment">
<t t-if="len(blog_post.message_ids) &lt;= 1" ><t t-esc="len(blog_post.message_ids)"/> comment</t>
<t t-if="len(blog_post.message_ids) > 1"><t t-esc="len(blog_post.message_ids)"/> comments</t>
</a>
@ -69,7 +67,7 @@
<p class="post-meta text-muted text-center" t-if="len(blog_post.tag_ids)">
<span class="icon-tags"/>
<t t-foreach="blog_post.tag_ids" t-as="tag">
<a t-attf-href="/blog/tag/#{tag.id}" t-esc="tag.name"/> &amp;nbsp;
<a t-href="/blog/tag/#{tag.id}" t-esc="tag.name"/> &amp;nbsp;
</t>
</p>
</xpath>
@ -81,7 +79,14 @@
<t t-call="website.publish_management">
<t t-set="object" t-value="blog_post"/>
<t t-set="publish_edit" t-value="True"/>
<t t-set="publish_duplicate" t-value="'/blog/%s/duplicate' % (blog_post.id)"/>
<li>
<a href="#" t-attf-data-href="/blog/#{blog_post.id}/duplicate">Duplicate</a>
<script>
var $a=$("[data-href$='/duplicate']");
$a.attr("href", $a.data('href')).removeAttr('data-href');
$a.next("script").remove();
</script>
</li>
</t>
</div>
<div class="clearfix"/>
@ -90,7 +95,7 @@
<p class="post-meta text-muted text-center" name="blog_post_data">
<span class="icon-calendar"> <span t-field="blog_post.create_date"/></span> &amp;nbsp;
<span class="icon-user"> By <span t-field="blog_post.create_uid"/> &amp;nbsp;</span>
<span t-if="len(blog_post.message_ids) &gt; 0" class="icon-comment"> With
<span t-if="len(blog_post.message_ids) &gt; 0" class="icon-comment"> With
<a t-attf-href="#comments">
<t t-if="len(blog_post.message_ids) &lt;= 1" ><t t-esc="len(blog_post.message_ids)"/> comment</t>
<t t-if="len(blog_post.message_ids) > 1"><t t-esc="len(blog_post.message_ids)"/> comments</t>
@ -132,17 +137,19 @@
<template id="opt_blog_post_complete_comment" name="Comment Form"
inherit_option_id="website_blog.blog_post_complete" inherit_id="website_blog.blog_post_complete">
<xpath expr="//ul[last()]" position="after">
<section groups="group_website_blog_reply" class="mb32">
<h4>Leave a Comment</h4>
<form id="comment" t-attf-action="/blog/#{blog_post.id}/post#post"
method="POST">
<img class="img pull-left img-rounded" t-att-src="'/website/image?model=res.partner&amp;field=image_small&amp;id='+str(user_id.partner_id.id)" style="width: 50px; margin-right: 10px;"/>
<div class="pull-left mb32" style="width: 75%%">
<textarea rows="3" class="form-control" placeholder="Write a comment..."></textarea>
<button type="submit" class="btn btn-danger mt8">Post</button>
</div>
</form>
</section>
<t t-if="not is_public_user">
<section groups="group_website_blog_reply" class="mb32">
<h4>Leave a Comment</h4>
<form id="comment" t-attf-action="/blog/#{blog_post.id}/comment"
method="POST">
<img class="img pull-left img-rounded" t-att-src="'/website/image?model=res.partner&amp;field=image_small&amp;id='+str(user_id.partner_id.id)" style="width: 50px; margin-right: 10px;"/>
<div class="pull-left mb32" style="width: 75%%">
<textarea rows="3" name="comment" class="form-control" placeholder="Write a comment..."></textarea>
<button type="submit" class="btn btn-danger mt8">Post</button>
</div>
</form>
</section>
</t>
</xpath>
</template>
@ -169,14 +176,14 @@
<p class="post-meta text-muted text-center" t-if="len(blog_post.tag_ids)">
<span class="icon-tags"/>
<t t-foreach="blog_post.tag_ids" t-as="tag">
<a t-attf-href="/blog/tag/#{tag.id}" t-esc="tag.name"/> &amp;nbsp;
<a t-href="/blog/tag/#{tag.id}" t-esc="tag.name"/> &amp;nbsp;
</t>
</p>
</xpath>
</template>
<!-- Page -->
<template id="index" name="Blog" page="True">
<!-- Page -->
<template id="index" name="Blog">
<t t-call="website.layout">
<t t-set="head">
<script type="text/javascript" src="/website_blog/static/src/js/website_blog.js"></script>
@ -187,8 +194,8 @@
<div class="row">
<div class="col-lg-12 col-sm-12" t-if="not blog_post" id="blog_post">
<t t-if="category and editable">
<div class="row">
<a t-attf-href="/blog/#{category.id}/new" class="btn btn-primary pull-right">New Blog Post</a>
<div class="row hidden-xs">
<a t-href="/blog/#{category.id}/new" class="btn btn-primary pull-right">New Blog Post</a>
</div>
</t>
<t t-foreach="blog_posts" t-as="blog_post" data-publish="">
@ -301,7 +308,7 @@
<ul class="nav nav-pills nav-stacked">
<t t-foreach="categories" t-as="nav_category">
<li>
<a t-attf-href="/blog/#{nav_category.id}">
<a t-href="/blog/#{nav_category.id}">
<span t-field="nav_category.name"/>
</a>
</li>
@ -311,5 +318,11 @@
</xpath>
</template>
<template id="blog_archive_link">
<li>
<a t-href="/blog/#{blog_post.id}"><t t-esc="blog_post.name"/></a>
</li>
</template>
</data>
</openerp>

View File

@ -55,7 +55,7 @@
</xpath>
</template>
<template id="contactus_thanks" name="Contact us" page="True">
<template id="contactus_thanks" name="Contact us">
<t t-call="website.layout">
<div id="wrap">
<div class="oe_structure"/>

View File

@ -28,7 +28,7 @@ class WebsiteCrmPartnerAssign(http.Controller):
if request.context['is_public_user']:
base_partner_domain += [('website_published', '=', True)]
partner_domain = list(base_partner_domain)
if grade_id:
if grade_id and grade_id != "all":
partner_domain += [('grade_id', '=', int(grade_id))] # try/catch int
if country_id:
country = country_obj.browse(request.cr, request.uid, country_id, request.context)
@ -94,7 +94,7 @@ class WebsiteCrmPartnerAssign(http.Controller):
def partners_ref(self, partner_id=0, **post):
partner_obj = request.registry['res.partner']
if request.context['is_public_user']:
partner_ids = partner_obj.search(request.cr, openerp.SUPERUSER_ID, [('website_pushished', '=', True), ('id', '=', partner_id)], context=request.context)
partner_ids = partner_obj.search(request.cr, openerp.SUPERUSER_ID, [('website_published', '=', True), ('id', '=', partner_id)], context=request.context)
else:
partner_ids = partner_obj.search(request.cr, request.uid, [('id', '=', partner_id)], context=request.context)
if not partner_ids:

View File

@ -4,7 +4,7 @@
<!-- Layout add nav and footer -->
<template id="footer_custom" inherit_id="website.layout" name="Custom Footer">
<template id="footer_custom" inherit_id="website.layout" name="Footer Partners Link">
<xpath expr="//footer//div[@name='info']/ul" position="inside">
<li><a t-href="/partners/">Partners</a></li>
</xpath>

View File

@ -14,7 +14,7 @@ class WebsiteCustomer(http.Controller):
@website.route([
'/customers/', '/customers/page/<int:page>/',
'/customers/country/<int:country_id>', '/customers/country/<int:country_id>/page/<int:page>/'
], type='http', auth="public")
], type='http', auth="public", multilang=True)
def customers(self, country_id=None, page=0, **post):
cr, uid, context = request.cr, request.uid, request.context
partner_obj = request.registry['res.partner']
@ -70,7 +70,7 @@ class WebsiteCustomer(http.Controller):
}
return request.website.render("website_customer.index", values)
@website.route(['/customers/<int:partner_id>/'], type='http', auth="public")
@website.route(['/customers/<int:partner_id>/'], type='http', auth="public", multilang=True)
def customer(self, partner_id=None, **post):
""" Route for displaying a single partner / customer.

View File

@ -3,13 +3,13 @@
<data>
<!-- Layout add nav and footer -->
<template id="footer_custom" inherit_id="website.layout" name="Custom Footer">
<template id="footer_custom" inherit_id="website.layout" name="Footer Customer References Link">
<xpath expr="//footer//div[@name='info']/ul" position="inside">
<li><a href="/customers/">Customer References</a></li>
<li><a t-href="/customers/">Customer References</a></li>
</xpath>
</template>
<!-- Page -->
<!-- Page -->
<template id="layout" name="Customer References Layout">
<t t-call="website.layout">
<t t-set="additional_title">Customer References</t>
@ -53,7 +53,7 @@
<t t-set="classname" t-value="'pull-left'"/>
</t>
<form action="/customers/" method="get" class="navbar-search pull-right pagination form-inline">
<div class="form-group">
<div class="form-group">
<input type="text" name="search" class="search-query form-control"
placeholder="Search" t-att-value="post.get('search', '')"/>
</div>
@ -63,11 +63,11 @@
<div>
<div t-foreach="partner_ids" t-as="partner" class="media thumbnail" data-publish="">
<t t-call="website.publish_management"><t t-set="object" t-value="partner"/></t>
<a class="pull-left" t-attf-href="/customers/#{ partner.id }/">
<a class="pull-left" t-href="/customers/#{ partner.id }/">
<img class="media-object" t-att-src="partner.img('image_small')"/>
</a>
<div class="media-body" style="min-height: 64px;">
<a class="media-heading" t-attf-href="/customers/#{ partner.id }/"><span t-field="partner.parent_id"/> <span t-field="partner.name"/></a>
<a class="media-heading" t-href="/customers/#{ partner.id }/"><span t-field="partner.parent_id"/> <span t-field="partner.name"/></a>
<div t-field="partner.website_short_description"/>
</div>
</div>

View File

@ -15,6 +15,7 @@ Online Events
'data': [
'data/event_data.xml',
'views/website_event.xml',
'views/website_event_sale_backend.xml',
'security/ir.model.access.csv',
'security/website_event.xml',
],

View File

@ -25,6 +25,9 @@ from openerp.addons.web.http import request
from openerp.tools.translate import _
from openerp.addons import website_sale
from openerp.addons.website.models import website
from openerp.addons.website.controllers.main import Website as controllers
controllers = controllers()
from datetime import datetime
from dateutil.relativedelta import relativedelta
@ -76,13 +79,14 @@ class website_event(http.Controller):
]
# search domains
current_date = dates[0][1]
current_date = None
current_type = None
current_country = None
for date in dates:
if searches["date"] == date[0]:
domain_search["date"] = date[2]
current_date = date[1]
if date[0] != 'all':
current_date = date[1]
if searches["type"] != 'all':
current_type = type_obj.browse(cr, uid, int(searches['type']), context=context)
domain_search["type"] = [("type", "=", int(searches["type"]))]
@ -182,7 +186,10 @@ class website_event(http.Controller):
_values = None
for key, value in post.items():
quantity = int(value)
try:
quantity = int(value)
except:
quantity = None
ticket_id = key.split("-")[0] == 'ticket' and int(key.split("-")[1]) or None
if not ticket_id or not quantity:
continue
@ -213,3 +220,15 @@ class website_event(http.Controller):
if not _values:
return request.redirect("/event/%s/" % event_id)
return request.redirect("/shop/checkout")
@website.route(['/event/publish'], type='json', auth="public")
def publish(self, id, object):
# if a user publish an event, he publish all linked res.partner
event = request.registry[object].browse(request.cr, request.uid, int(id))
if not event.website_published:
if event.organizer_id and not event.organizer_id.website_published:
event.organizer_id.write({'website_published': True})
if event.address_id and not event.address_id.website_published:
event.address_id.write({'website_published': True})
return controllers.publish(id, object)

View File

@ -2,6 +2,12 @@
<openerp>
<data noupdate="1">
<record id="menu_events" model="website.menu">
<field name="name">Events</field>
<field name="url">/event</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence" type="int">40</field>
</record>
<record id="action_open_website" model="ir.actions.act_url">
<field name="name">Website Home</field>
<field name="target">self</field>

View File

@ -2,6 +2,9 @@
<openerp>
<data>
<record id="base.res_partner_6" model="res.partner">
<field name="website_published">True</field>
</record>
<record id="event.event_0" model="event.event">
<field name="website_published">True</field>
<field name="twitter_hashtag">openerp</field>
@ -162,6 +165,9 @@
</div>
]]></field> </record>
<record id="base.res_partner_5" model="res.partner">
<field name="website_published">True</field>
</record>
<record id="event.event_1" model="event.event">
<field name="website_published">True</field>
<field name="twitter_hashtag">openerp</field>
@ -323,11 +329,20 @@
]]></field>
</record>
<record id="base.res_partner_14" model="res.partner">
<field name="website_published">True</field>
</record>
<record id="event.event_2" model="event.event">
<field name="website_published">True</field>
<field name="twitter_hashtag">openerp</field>
</record>
<record id="base.res_partner_2" model="res.partner">
<field name="website_published">True</field>
</record>
<record id="base.res_partner_address_4" model="res.partner">
<field name="website_published">True</field>
</record>
<record id="event.event_3" model="event.event">
<field name="website_published">True</field>
<field name="twitter_hashtag">openerp</field>

View File

@ -51,6 +51,21 @@ class event(osv.osv):
'website_published': False,
}
def _check_organizer_id_published(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
if obj.website_published and obj.organizer_id and not obj.organizer_id.website_published:
return False
return True
def _check_address_id_published(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
if obj.website_published and obj.address_id and not obj.address_id.website_published:
return False
return True
_constraints = [
(_check_organizer_id_published, "This event can't be published if the field Orginizer is not website published.", ['organizer_id','website_published']),
(_check_address_id_published, "This event can't be published if the field Location is not website published.", ['address_id','website_published']),
]
def google_map_img(self, cr, uid, ids, zoom=8, width=298, height=298, context=None):
partner = self.browse(cr, uid, ids[0], context=context)
if partner.address_id:

View File

@ -1,4 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_event_event_public,event.event.public,event.model_event_event,base.group_public,1,0,0,0
access_event_type_public,event.type.public,event.model_event_type,base.group_public,1,0,0,0
access_event_event_ticket_public,event.event.ticket.public,event_sale.model_event_event_ticket,base.group_public,1,0,0,0
access_event_product_product_public,event.product.product.public,product.model_product_product,base.group_public,1,0,0,0
access_event_product_product_public,event.product.product.public,product.model_product_product,base.group_public,1,0,0,0

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_event_event_public event.event.public event.model_event_event base.group_public 1 0 0 0
3 access_event_type_public event.type.public event.model_event_type base.group_public 1 0 0 0
4 access_event_event_ticket_public event.event.ticket.public event_sale.model_event_event_ticket base.group_public 1 0 0 0
5 access_event_product_product_public event.product.product.public product.model_product_product base.group_public 1 0 0 0

View File

@ -3,34 +3,30 @@
<data>
<!-- Layout add nav and footer -->
<template id="header_footer_custom" inherit_id="website.layout">
<xpath expr="//header//ul[@id='top_menu']/li[@name='contactus']" position="before">
<li><a t-href="/event">Events</a></li>
</xpath>
<xpath expr="//footer//div[@name='info']/ul" position="inside">
<template id="header_footer_custom" inherit_id="website.layout" name="Footer Events Link">
<xpath expr="//footer//ul[@name='products']" position="inside">
<li><a t-href="/event">Events</a></li>
</xpath>
</template>
<!-- Page -->
<template id="index" name="Events" page="True">
<!-- Page -->
<template id="index" name="Events">
<t t-call="website.layout">
<div id="wrap">
<div class="oe_structure"/>
<div class="container">
<div class="oe_structure"/>
<h1 class="text-center">
Our Events
Next Events
</h1>
<h3 class="text-center text-muted">
<t t-esc="current_date"/><t t-if="current_type">,
<t t-esc="current_type.name"/></t><t t-if="current_country">,
<t t-esc="current_date or ''"/><span t-if="current_type"><t t-if="current_date">,</t>
<t t-esc="current_type.name"/></span><span t-if="current_country"><t t-if="current_type or current_date">,</t>
<t t-esc="current_country.name"/>
</t>
</span>
</h3>
<div class="row mt16 mb32">
<div class="col-md-3 col-sm-4 css_noprint" id="left_column">
<ul class="nav nav-pills nav-stacked">
<li class="nav-header">Date</li>
<t t-foreach="dates" t-as="date">
<li t-att-class="searches.get('date') == date[0] and 'active' or ''" t-if="date[3]">
<a t-href="/event/#{ search_path }&amp;date=#{ date[0] }"><t t-esc="date[1]"/>
@ -40,7 +36,7 @@
</t>
</ul>
</div>
<div class="col-sm-8 col-md-9">
<div class="col-sm-7 col-md-6" id="middle_column">
<t t-call="website.pager" >
<t t-set="classname">pull-left</t>
</t>
@ -48,14 +44,11 @@
</div>
<ul class="media-list">
<li t-foreach="event_ids" t-as="event" class="media" data-publish="">
<t t-call="website.publish_management"><t t-set="object" t-value="event"/></t>
<div class="media-body">
<span t-if="not event.event_ticket_ids" class="label label-danger pull-right">Registration Closed</span>
<t t-if="event.event_ticket_ids">
<span t-if="event.register_avail == 9999" class="label label-default pull-right label-info">Tickets Available</span>
<span t-if="not event.register_avail" class="label label-danger pull-right">Sold Out</span>
<span t-if="event.register_avail and event.register_avail != 9999" t-attf-class="label label-default pull-right label-#{ event.register_avail &lt;= 10 and 'warning' or 'info' }">
Tickets Available
<span t-if="event.register_avail and event.register_avail &lt;= ((event.register_max or 0) / 4)" class="label pull-right label-info">
Only <t t-esc="event.register_avail"/> Remaining
</span>
</t>
<h4 class="media-heading"><a t-href="/event/#{ event.id }/"><span t-field="event.name"> </span></a></h4>
@ -76,6 +69,10 @@
</div>
</li>
</ul>
</div>
<div class="col-sm-2 col-md-3 oe_structure" id="right_column">
</div>
<div class="col-md-8 col-lg-offset-4 text-center">
<t t-call="website.pager" />
@ -87,10 +84,45 @@
</t>
</template>
<template id="event_category" inherit_id="website_event.index" inherit_option_id="website_event.index" name="Category">
<template id="event_right_photos" inherit_id="website_event.index" inherit_option_id="website_event.index" name="Photos">
<xpath expr="//div[@id='right_column']" position="inside">
<div class="row">
<div class="col-md-12 mb16">
<div class="oe_demo">
<img src="http://ebmedia.eventbrite.com/s3-build/17022-rc2013-11-04-acfb89e/django/images/pages/home/featcats_bizpro.jpg"/>
<div class="text-center"><a href="/">Photos of Events</a></div>
</div>
</div>
<div class="col-md-12 mb16">
<div class="oe_demo">
<img src="http://ebmedia.eventbrite.com/s3-build/17022-rc2013-11-04-acfb89e/django/images/pages/home/featcats_bizpro.jpg"/>
<div class="text-center"><a href="/">Customer Quotes</a></div>
</div>
</div>
</div>
</xpath>
</template>
<template id="event_right_quotes" inherit_id="website_event.index" inherit_option_id="website_event.index" name="Quotes">
<xpath expr="//div[@id='right_column']" position="inside">
<div class="row">
<div class="col-md-12 mb16">
<blockquote class="oe_snippet_body">
<p>
Write here a quote from one of your attendees.
It gives confidence in your
events.
</p>
<small>Author</small>
</blockquote>
</div>
</div>
</xpath>
</template>
<template id="event_category" inherit_id="website_event.index" inherit_option_id="website_event.index" name="Filter by Category">
<xpath expr="//div[@id='left_column']" position="inside">
<ul class="nav nav-pills nav-stacked mt32">
<li class="nav-header">Category</li>
<t t-foreach="types">
<li t-if="type" t-att-class="searches.get('type') == str(type and type[0]) and 'active' or ''">
<a t-href="/event/#{ search_path }&amp;type=#{ type[0] }"><t t-esc="type[1]"/>
@ -101,10 +133,9 @@
</ul>
</xpath>
</template>
<template id="event_location" inherit_id="website_event.index" inherit_option_id="website_event.index" name="Location">
<template id="event_location" inherit_id="website_event.index" inherit_option_id="website_event.index" name="Filter by Country">
<xpath expr="//div[@id='left_column']" position="inside">
<ul class="nav nav-pills nav-stacked mt32">
<li class="nav-header">Location</li>
<t t-foreach="countries">
<li t-if="country_id" t-att-class="searches.get('country') == str(country_id and country_id[0]) and 'active' or ''">
<a t-href="/event/#{ search_path }&amp;country=#{ country_id[0] }"><t t-esc="country_id[1]"/>
@ -124,7 +155,7 @@
<h1 class="text-center" t-field="event_id.name"></h1>
<h4 class="text-center text-muted">
<i class="icon-time"></i> <span t-field="event_id.date_begin"/> to
<i class="icon-time"></i> <span t-field="event_id.date_begin"/> to
<span t-field="event_id.date_end"/>
</h4>
<h4 t-if="event_id.city and event_id.country_id" class="text-center text-muted">
@ -177,7 +208,11 @@
<ul class="media-list" id="comment">
<li t-foreach="event_id.website_message_ids" t-as="comment" class="media">
<div class="media-body">
<t t-call="website.publish_management"><t t-set="object" t-value="comment"/></t>
<t t-call="website.publish_management">
<t t-set="object" t-value="comment"/>
<t t-set="publish_edit" t-value="True"/>
<t t-set="publish_controller">/event/publish</t>
</t>
<t t-raw="comment.body"/>
<small class="pull-right muted text-right">
<div t-field="comment.author_id"/>
@ -192,7 +227,12 @@
<div class="panel panel-default" t-if="event_id.address_id">
<div class="panel-heading">
<t t-call="website.publish_management"><t t-set="object" t-value="event_id"/></t>
<t t-call="website.publish_management">
<t t-set="object" t-value="event_id"/>
<t t-set="publish_edit" t-value="True"/>
<t t-set="publish_controller">/event/publish</t>
<t t-set="style">position: relative; top: -80px;</t>
</t>
<h4>Where</h4>
</div>
<div class="panel-body">
@ -262,12 +302,12 @@
<ul class="list-unstyled">
<li t-if="event_id.type">
<a t-att-href="'/event?type='+str(event_id.type.id)"><strong><span class="icon-double-angle-right"/> Other <t t-esc="event_id.type.name"/></strong></a>
<a t-href="/event?type=#{event_id.type.id}"><strong><span class="icon-double-angle-right"/> Other <t t-esc="event_id.type.name"/></strong></a>
</li>
<li t-if="event_id.country_id">
<a t-att-href="'/event?country='+str(event_id.country_id.id)"><strong><span class="icon-double-angle-right"/> Other Events in <span t-esc="event_id.country_id.name"/></strong></a>
<a t-href="/event?country=#{event_id.country_id.id}"><strong><span class="icon-double-angle-right"/> Other Events in <span t-esc="event_id.country_id.name"/></strong></a>
</li>
<li><a href="/event"><strong><span class="icon-double-angle-right"/> All Events</strong></a></li>
<li><a t-href="/event"><strong><span class="icon-double-angle-right"/> All Events</strong></a></li>
</ul>
</div>
</div>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_event_form">
<field name="name">event.event.website.form</field>
<field name="model">event.event</field>
<field name="inherit_id" ref="event_sale.view_event_form"/>
<field name="arch" type="xml">
<field name="organizer_id" position="after">
<field name="website_published"/>
</field>
</field>
</record>
</data>
</openerp>

View File

@ -11,4 +11,4 @@ class hr(osv.osv):
}
def img(self, cr, uid, ids, field='image_small', context=None):
return "/web/binary/image?model=%s&field=%s&id=%s" % (self._name, field, ids[0])
return "/website/image?model=%s&field=%s&id=%s" % (self._name, field, ids[0])

View File

@ -2,13 +2,16 @@
from openerp.addons.web import http
from openerp.addons.web.http import request
from openerp.addons.website.models import website
from openerp.addons.website.controllers.main import Website as controllers
controllers = controllers()
import base64
class website_hr_recruitment(http.Controller):
@website.route(['/jobs', '/jobs/page/<int:page>/', '/department/<id>/', '/department/<id>/page/<int:page>/'], type='http', auth="public")
def jobs(self, id=0, page=1, **post):
@website.route(['/jobs', '/jobs/page/<int:page>/', '/department/<id>/', '/department/<id>/page/<int:page>/'], type='http', auth="public", multilang=True)
def jobs(self, id=0, page=1):
id = id and int(id) or 0
hr_job_obj = request.registry['hr.job']
hr_department_obj = request.registry['hr.department']
@ -36,7 +39,7 @@ class website_hr_recruitment(http.Controller):
step = 10
pager = request.website.pager(url="/jobs/", total=len(jobpost_ids), page=page, step=step, scope=5)
jobpost_ids = hr_job_obj.search(request.cr, request.uid, domain, limit=step, offset=pager['offset'])
values = {
'active': active,
'companies': companies,
@ -47,16 +50,15 @@ class website_hr_recruitment(http.Controller):
}
return request.website.render("website_hr_recruitment.index", values)
@website.route(['/job/detail/<id>'], type='http', auth="public")
def detail(self, id=0):
id = id and int(id) or 0
@website.route(['/job/detail/<model("hr.job"):job>'], type='http', auth="public", multilang=True)
def detail(self, job):
values = {
'job': request.registry['hr.job'].browse(request.cr, request.uid, id),
'vals_date': request.registry['hr.job'].browse(request.cr, request.uid, id).write_date.split(' ')[0]
'job': job,
'vals_date': job.write_date.split(' ')[0],
}
return request.website.render("website_hr_recruitment.detail", values)
@website.route(['/job/success'], type='http', auth="admin")
@website.route(['/job/success'], type='http', auth="admin", multilang=True)
def success(self, **post):
id = request.registry['hr.applicant'].create(request.cr, request.uid, post)
if post['ufile']:
@ -69,28 +71,23 @@ class website_hr_recruitment(http.Controller):
'res_id': id
}
request.registry['ir.attachment'].create(request.cr, request.uid, attachment_values)
website = request.registry['website']
values = {
'jobid': post['job_id']
}
return request.website.render("website_hr_recruitment.thankyou", values)
@website.route(['/apply/<int:id>'], type='http', auth="public")
def applyjobpost(self, id=0):
id = id and int(id) or 0
job = request.registry['hr.job'].browse(request.cr, request.uid, id)
values = {
'job': job
}
return request.website.render("website_hr_recruitment.applyjobpost", values)
@website.route('/recruitment/published', type='json', auth="admin")
def published (self, id, **post):
hr_job = request.registry['hr.job']
@website.route(['/apply/<model("hr.job"):job>'], type='http', auth="public", multilang=True)
def applyjobpost(self, job):
return request.website.render("website_hr_recruitment.applyjobpost", { 'job': job })
@website.route('/job/publish', type='json', auth="admin", multilang=True)
def publish (self, id, object):
res = controllers.publish(id, object)
hr_job = request.registry[object]
id = int(id)
rec = hr_job.browse(request.cr, request.uid, id)
vals = {}
if rec.website_published:
vals['state'] = 'recruit'
if not rec.no_of_recruitment:
@ -99,6 +96,5 @@ class website_hr_recruitment(http.Controller):
vals['state'] = 'open'
hr_job.write(request.cr, request.uid, [rec.id], vals, context=request.context)
obj = hr_job.browse(request.cr, request.uid, id, context=request.context)
return { 'count': obj.no_of_recruitment, 'state': obj.state, 'published': obj.website_published }
return res
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,7 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data noupdate="1">
<record id="menu_jobs" model="website.menu">
<field name="name">Jobs</field>
<field name="url">/jobs</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence" type="int">60</field>
</record>
<record id="action_open_website" model="ir.actions.act_url">
<field name="name">Website Recruitment Form</field>
<field name="target">self</field>

View File

@ -1,11 +0,0 @@
$(function () {
$(this).ajaxComplete(function(event, xhr, settings) {
var data = JSON.parse(settings.data).params;
if (settings.url == "/website/publish") {
var $data = $(".oe_website_hr_recruitment .js_publish_management[data-id='" + data.id + "'][data-object='" + data.object + "']");
if ($data.length) {
settings.jsonpCallback = openerp.jsonRpc('/recruitment/published', 'call', data);
}
}
});
});

View File

@ -7,16 +7,13 @@
<field name="description">Job Posts on your website</field>
</record>
<template id="job_footer_custom" inherit_id="website.layout" name="Custom Footer Job">
<xpath expr="//header//ul[@id='top_menu']/li[@name='contactus']" position="before">
<li><a href="/jobs">Jobs</a></li>
</xpath>
<template id="job_footer_custom" inherit_id="website.layout" name="Footer Job Link">
<xpath expr="//footer//div[@name='info']/ul" position="inside">
<li><a href="/jobs">Jobs</a></li>
<li><a t-href="/jobs">Jobs</a></li>
</xpath>
</template>
<template id="index" name="Jobs" page="True">
<template id="index" name="Jobs">
<t t-call="website.layout">
<div id="wrap">
<div class="oe_structure"/>
@ -36,7 +33,7 @@
<t t-if="job.no_of_recruitment">
<span class="label label-default pull-right label-info"><t t-esc="vals[job.id]['count']"/> Vacancies.</span>
</t>
<h4 class="media-heading"><a t-attf-href="/job/detail/#{ job.id }/"><span t-field="job.name"> </span></a></h4>
<h4 class="media-heading"><a t-href="/job/detail/#{ job.id }/"><span t-field="job.name"> </span></a></h4>
<div t-if="companies[0].country_id">
<i class="icon-map-marker"/> <span t-field="companies[0].city"> </span> <span t-if="companies[0].state_id" t-field="companies[0].state_id.name"> </span>, <span t-field="companies[0].country_id.name"> </span>
</div>
@ -56,10 +53,6 @@
<template id="detail">
<t t-call="website.layout">
<t t-set="head">
<script type="text/javascript" src="/website_hr_recruitment/static/src/js/recruitment.js"></script>
<t t-raw="head or ''"/>
</t>
<t t-set="additional_title">Job Detail</t>
<div id="wrap">
<div class="container oe_website_hr_recruitment">
@ -73,6 +66,7 @@
<t t-call="website.publish_management">
<t t-set="object" t-value="job"/>
<t t-set="publish_edit" t-value="True"/>
<t t-set="publish_controller">/job/publish</t>
</t>
</div>
</div>
@ -83,7 +77,7 @@
<h5 class="text-center text-muted">
<i class="icon-time"/> <span><t t-esc="vals_date"/></span>
</h5>
</div>
</div>
<div class="container oe_structure">
<div class="row">
<div t-if="job.website_description">
@ -92,11 +86,11 @@
<div class="container">
<div class="row">
<div class="col-md-12 text-center mt16 mb16">
<a t-attf-href="/apply/#{ job.id }/" class="btn btn-primary btn-lg">Apply</a>
<a t-href="/apply/#{ job.id }/" class="btn btn-primary btn-lg">Apply</a>
</div>
</div>
</div>
</section>
</section>
</div>
</div>
</div>
@ -112,6 +106,7 @@
<h1 class="text-center">Apply for <span t-field="job.name"/></h1>
<div class="row">
<section id="forms">
<!-- TODO Multilingual form action support ? -->
<form class="form-horizontal mt32" action="/job/success" method="post" enctype="multipart/form-data">
<input type="hidden" t-att-value="job.department_id.id" name="department_id"/>
<input type="hidden" t-att-value="job.id" name="job_id"/>
@ -170,10 +165,10 @@
<xpath expr="//div[@id='jobs_grid']" position="before">
<div class="col-md-3">
<ul class="nav nav-pills nav-stacked mt16">
<li t-att-class=" '' if active else 'active' "><a href="/jobs">All Departments</a></li>
<li t-att-class=" '' if active else 'active' "><a t-href="/jobs">All Departments</a></li>
<t t-foreach="departments" t-as="department">
<li t-att-class="department.id == active and 'active' or ''">
<a t-attf-href="/department/#{ department.id }/" ><span t-field="department.name"/></a>
<a t-href="/department/#{ department.id }/" ><span t-field="department.name"/></a>
</li>
</t>
</ul>

View File

@ -3,17 +3,20 @@
<data>
<template id="follow">
<div name="follow">
<a href="#" t-att-data-id="object.id" t-att-data-object="object._name" t-att-data-follow="object.id and object.message_is_follower and 'on' or 'off'"
class="pull-right js_follow" t-if="editable" t-ignore="true">
<div name="follow" t-if="editable">
<a href="#" t-att-data-id="object.id"
t-att-data-object="object._name"
t-att-data-follow="object.id and object.message_is_follower and 'on' or 'off'"
class="pull-right js_follow" t-ignore="true">
<span t-attf-class="label label-success css_follow">Follow</span>
<span t-attf-class="label label-danger css_unfollow">Unfollow</span>
<span t-attf-class="label label-success css_followed">Following</span>
<span t-attf-class="label label-danger css_unfollowed">Not Following</span>
</a>
<input type="email" name="email" class="js_follow_email css_unfollowed_email"
t-att-value="email"
t-att-placeholder="email or 'Email Address'"/>
<input type="email" name="email"
class="js_follow_email css_unfollowed_email"
t-att-value="email"
t-att-placeholder="email or 'Email Address'"/>
</div>
</template>

View File

@ -84,7 +84,7 @@ class WebsiteMembership(http.Controller):
def partners_ref(self, partner_id=0, **post):
partner_obj = request.registry['res.partner']
if request.context['is_public_user']:
partner_ids = partner_obj.search(request.cr, openerp.SUPERUSER_ID, [('website_pushished', '=', True), ('id', '=', partner_id)], context=request.context)
partner_ids = partner_obj.search(request.cr, openerp.SUPERUSER_ID, [('website_published', '=', True), ('id', '=', partner_id)], context=request.context)
else:
partner_ids = partner_obj.search(request.cr, request.uid, [('id', '=', partner_id)], context=request.context)
if not partner_ids:

View File

@ -3,7 +3,7 @@
<data>
<!-- Layout add nav and footer -->
<template id="footer_custom" inherit_id="website.layout" name="Custom Footer">
<template id="footer_custom" inherit_id="website.layout" name="Footer Associations Link">
<xpath expr="//footer//div[@name='info']/ul" position="inside">
<li><a t-href="/members/">Associations</a></li>
</xpath>
@ -103,7 +103,7 @@
<!-- Option: index: Left Google Map -->
<template id="opt_index_google_map" name="Left World Map"
inherit_option_id="website_membership.index" inherit_id="website_membership.index">
<xpath expr="//div[@id='left_column']/ul[last()]" position="after">
<xpath expr="//div[@id='left_column']/ul[1]" position="before">
<ul class="nav nav-pills nav-stacked mt16">
<li class="nav-header"><h3>World Map</h3></li>
<ul class="nav">

View File

@ -28,13 +28,13 @@ from openerp.osv import osv
class Website(osv.Model):
_inherit = "website"
def preprocess_request(self, cr, uid, ids, *args, **kwargs):
def preprocess_request(self, cr, uid, ids, request, context=None):
project_obj = request.registry['project.project']
project_ids = project_obj.search(cr, uid, [('privacy_visibility', "=", "public")], context=request.context)
request.context['website_project_ids'] = project_obj.browse(cr, uid, project_ids, request.context)
return super(Website, self).preprocess_request(cr, uid, ids, *args, **kwargs)
return super(Website, self).preprocess_request(cr, uid, ids, request, context)
class website_project(http.Controller):

View File

@ -3,9 +3,9 @@
<data>
<!-- Layout add nav and footer -->
<template id="footer_custom" inherit_id="website.layout" name="Custom Footer">
<template id="footer_custom" inherit_id="website.layout" name="Footer Project's Links">
<xpath expr="//footer//ul[@name='products']" position="inside">
<li t-foreach="website_project_ids" t-as="project"><a t-href="/project/#{ project.id }/"><t t-esc="project.name"/></a></li>
<li t-foreach="website_project_ids" t-as="project"><a t-href="/project/#{ project.id }/" t-field="project.name"/></li>
</xpath>
</template>

View File

@ -1,14 +1,15 @@
# -*- coding: utf-8 -*-
import random
import uuid
import simplejson
import werkzeug.exceptions
from openerp import SUPERUSER_ID
from openerp.osv import osv
from openerp.addons.web import http
from openerp.addons.web.http import request
from openerp.addons.website.models import website
import random
import uuid
import urllib
import simplejson
def get_order(order_id=None):
order_obj = request.registry.get('sale.order')
@ -52,11 +53,11 @@ def get_current_order():
class Website(osv.osv):
_inherit = "website"
def preprocess_request(self, cr, uid, ids, *args, **kwargs):
def preprocess_request(self, cr, uid, ids, request, context=None):
request.context.update({
'website_sale_order': get_current_order(),
})
return super(Website, self).preprocess_request(cr, uid, ids, *args, **kwargs)
return super(Website, self).preprocess_request(cr, uid, ids, request, context=None)
class Ecommerce(http.Controller):
@ -212,7 +213,6 @@ class Ecommerce(http.Controller):
col += 1
line += 1
print bin_packing_list
return bin_packing_list
def get_products(self, product_ids):
@ -222,20 +222,24 @@ class Ecommerce(http.Controller):
product_ids = [id for id in product_ids if id in product_obj.search(request.cr, request.uid, [("id", 'in', product_ids)], context=request.context)]
return product_obj.browse(request.cr, request.uid, product_ids, context=request.context)
def has_search_attributes(self, attribute_id, value_id=None):
if request.httprequest.args.get('attributes'):
attributes = simplejson.loads(request.httprequest.args['attributes'])
def has_search_filter(self, attribute_id, value_id=None):
if request.httprequest.args.get('filter'):
filter = simplejson.loads(request.httprequest.args['filter'])
else:
attributes = []
for key_val in attributes:
filter = []
for key_val in filter:
if key_val[0] == attribute_id and (not value_id or value_id in key_val[1:]):
return key_val
return False
@website.route(['/shop/attributes/'], type='http', auth="public", multilang=True)
def attributes(self, **post):
attributes = []
@website.route(['/shop/filter/'], type='http', auth="public", multilang=True)
def filter(self, add_filter="", **post):
index = []
filter = []
if add_filter:
filter = simplejson.loads(add_filter)
for filt in filter:
index.append(filt[0])
for key, val in post.items():
cat = key.split("-")
if len(cat) < 3 or cat[2] in ('max','minmem','maxmem'):
@ -247,16 +251,23 @@ class Ecommerce(http.Controller):
_max = int(post.pop("att-%s-max" % cat[1]))
_min = int(val)
if (minmem != _min or maxmem != _max) and cat_id not in index:
attributes.append([cat_id , [_min, _max] ])
filter.append([cat_id , [_min, _max] ])
index.append(cat_id)
elif cat_id not in index:
attributes.append([ cat_id, int(cat[2]) ])
filter.append([ cat_id, int(cat[2]) ])
index.append(cat_id)
else:
attributes[index.index(cat_id)].append( int(cat[2]) )
cat[2] = int(cat[2])
if cat[2] not in filter[index.index(cat_id)][1:]:
filter[index.index(cat_id)].append( cat[2] )
post.pop(key)
return request.redirect("/shop/?attributes=%s&%s" % (simplejson.dumps(attributes).replace(" ", ""), urllib.urlencode(post)))
return request.redirect("/shop/?filter=%s%s%s%s" % (
simplejson.dumps(filter),
add_filter and "&add_filter=%s" % add_filter or "",
post.get("search") and "&search=%s" % post.get("search") or "",
post.get("category") and "&category=%s" % post.get("category") or ""
))
def attributes_to_ids(self, attributes):
obj = request.registry.get('product.attribute.product')
@ -272,7 +283,7 @@ class Ecommerce(http.Controller):
return [r["product_id"][0] for r in att]
@website.route(['/shop/', '/shop/page/<int:page>/'], type='http', auth="public", multilang=True)
def category(self, category=0, attributes="", page=0, **post):
def category(self, category=0, filter="", page=0, **post):
# TDE-NOTE: shouldn't we do somethign about product_template without variants ???
# TDE-NOTE: is there a reason to call a method category when the route is
# basically a shop without category_id speceified ?
@ -294,10 +305,10 @@ class Ecommerce(http.Controller):
cat_id = int(category)
domain = [('product_variant_ids.public_categ_id.id', 'child_of', cat_id)] + domain
if attributes:
attributes = simplejson.loads(attributes)
if attributes:
ids = self.attributes_to_ids(attributes)
if filter:
filter = simplejson.loads(filter)
if filter:
ids = self.attributes_to_ids(filter)
domain = [('id', 'in', ids or [0] )] + domain
step = 20
@ -319,32 +330,33 @@ class Ecommerce(http.Controller):
'Ecommerce': self,
'product_ids': product_ids,
'product_ids_for_holes': fill_hole,
'search': post or dict(),
'search': {
'search': post.get('search') or '',
'category': category,
'filter': filter or '',
},
'pager': pager,
'styles': styles,
'style_in_product': lambda style, product: style.id in [s.id for s in product.website_style_ids],
}
return request.website.render("website_sale.products", values)
@website.route(['/shop/product/<int:product_id>/'], type='http', auth="public", multilang=True)
def product(self, product_id=0, **post):
@website.route(['/shop/product/<model("product.template"):product>/'], type='http', auth="public", multilang=True)
def product(self, product, search='', category='', filter='', promo=None, lang_code=None):
if 'promo' in post:
self.change_pricelist(post.get('promo'))
if promo:
self.change_pricelist(promo)
product_obj = request.registry.get('product.template')
category_obj = request.registry.get('product.public.category')
category_ids = category_obj.search(request.cr, request.uid, [(1, '=', 1)], context=request.context)
category_ids = category_obj.search(request.cr, request.uid, [], context=request.context)
category_list = category_obj.name_get(request.cr, request.uid, category_ids, context=request.context)
category_list = sorted(category_list, key=lambda category: category[1])
category = None
if post.get('category_id') and int(post.get('category_id')):
category = category_obj.browse(request.cr, request.uid, int(post.get('category_id')), context=request.context)
if category:
category = category_obj.browse(request.cr, request.uid, int(category), context=request.context)
request.context['pricelist'] = self.get_pricelist()
product = product_obj.browse(request.cr, request.uid, product_id, context=request.context)
values = {
'Ecommerce': self,
@ -352,15 +364,26 @@ class Ecommerce(http.Controller):
'category_list': category_list,
'main_object': product,
'product': product,
'search': post or dict(),
'search': {
'search': search,
'category': category and str(category.id),
'filter': filter,
}
}
return request.website.render("website_sale.product", values)
@website.route(['/shop/add_product/', '/shop/category/<int:cat_id>/add_product/'], type='http', auth="public", multilang=True)
def add_product(self, cat_id=0, **post):
product_id = request.registry.get('product.product').create(request.cr, request.uid,
{'name': 'New Product', 'public_categ_id': cat_id}, request.context)
return request.redirect("/shop/product/%s/?enable_editor=1" % product_id)
if request.httprequest.method != 'POST':
return werkzeug.exceptions.MethodNotAllowed(valid_methods=['POST'])
Product = request.registry.get('product.product')
product_id = Product.create(request.cr, request.uid, {
'name': 'New Product', 'public_categ_id': cat_id
}, context=request.context)
product = Product.browse(request.cr, request.uid, product_id, context=request.context)
return request.redirect("/shop/product/%s/?enable_editor=1" % product.product_tmpl_id.id)
def get_pricelist(self):
if not request.httprequest.session.get('ecommerce_pricelist'):
@ -443,8 +466,7 @@ class Ecommerce(http.Controller):
else:
order_line_id = order_line_obj.create(request.cr, SUPERUSER_ID, values, context=request.context)
order_obj.write(request.cr, SUPERUSER_ID, [order.id], {'order_line': [(4, order_line_id)]}, context=request.context)
return [quantity, order.get_total_quantity()]
return quantity
@website.route(['/shop/mycart/'], type='http', auth="public", multilang=True)
def mycart(self, **post):
@ -458,7 +480,7 @@ class Ecommerce(http.Controller):
product_ids = []
if order:
for line in order.order_line:
suggested_ids += [p.id for p in line.product_id and line.product_id.suggested_product_ids or [] for line in order.order_line]
suggested_ids += [p.id for p in line.product_id and line.product_id.suggested_product_ids or []]
product_ids.append(line.product_id.id)
suggested_ids = list(set(suggested_ids) - set(product_ids))
if suggested_ids:
@ -484,7 +506,11 @@ class Ecommerce(http.Controller):
@website.route(['/shop/add_cart_json/'], type='json', auth="public")
def add_cart_json(self, product_id=None, order_line_id=None, remove=None):
return self.add_product_to_cart(product_id=product_id, order_line_id=order_line_id, number=(remove and -1 or 1))
quantity = self.add_product_to_cart(product_id=product_id, order_line_id=order_line_id, number=(remove and -1 or 1))
order = get_current_order()
return [quantity, order.get_total_quantity(), order.amount_total, request.website.render("website_sale.total", {
'website_sale_order': order
}).strip()]
@website.route(['/shop/set_cart_json/'], type='json', auth="public")
def set_cart_json(self, path=None, product_id=None, order_line_id=None, set_number=0, json=None):

View File

@ -38,7 +38,7 @@ class attributes(osv.Model):
_columns = {
'name': fields.char('Name', size=64, translate=True, required=True),
'type': fields.selection([('distinct', 'Distinct'), ('float', 'Float')], "Type", required=True),
'type': fields.selection([('distinct', 'Textual Value'), ('float', 'Numeric Value')], "Type", required=True),
'value_ids': fields.one2many('product.attribute.value', 'attribute_id', 'Values'),
'product_ids': fields.one2many('product.attribute.product', 'attribute_id', 'Products'),
@ -48,9 +48,11 @@ class attributes(osv.Model):
'float_min': fields.function(_get_float_min, type='float', string="Min", store={
'product.attribute.product': (_get_min_max, ['value','attribute_id'], 20),
}),
'visible': fields.boolean('Display Filter on Website'),
}
_defaults = {
'type': 'distinct'
'type': 'distinct',
'visible': True,
}
class attributes_value(osv.Model):
@ -63,9 +65,10 @@ class attributes_value(osv.Model):
class attributes_product(osv.Model):
_name = "product.attribute.product"
_order = 'attribute_id, value_id, value'
_columns = {
'value': fields.float('Value'),
'value_id': fields.many2one('product.attribute.value', 'Distinct Value'),
'value': fields.float('Numeric Value'),
'value_id': fields.many2one('product.attribute.value', 'Textual Value'),
'attribute_id': fields.many2one('product.attribute', 'Attribute', required=True),
'product_id': fields.many2one('product.template', 'Product', required=True),

View File

@ -4,6 +4,10 @@ access_product_template_public,product.template.public,product.model_product_tem
access_product_category_,product.category.public,product.model_product_category,base.group_public,1,0,0,0
access_product_category_public,product.category.public,product.model_product_public_category,base.group_public,1,0,0,0
access_product_pricelist_version_public,product.pricelist.version.public,product.model_product_pricelist_version,base.group_public,1,0,0,0
access_product_pricelist_public,product.pricelist.public,product.model_product_pricelist,base.group_public,1,0,0,0
access_product_product_price_type_public,product.price.type.public,product.model_product_price_type,base.group_public,1,0,0,0
access_sale_order_public,sale.order.public,model_sale_order,base.group_public,1,0,0,0
access_sale_order_line_public,sale.order.line.public,model_sale_order_line,base.group_public,1,0,0,0
access_sale_order_line_public,sale.order.line.public,model_sale_order_line,base.group_public,1,0,0,0
access_product_attribute,product.attribute.public,website_sale.model_product_attribute,base.group_public,1,0,0,0
access_product_attribute_value,product.attribute.value.public,website_sale.model_product_attribute_value,base.group_public,1,0,0,0
access_product_attribute_product,product.attribute.product.public,website_sale.model_product_attribute_product,base.group_public,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
4 access_product_category_ product.category.public product.model_product_category base.group_public 1 0 0 0
5 access_product_category_public product.category.public product.model_product_public_category base.group_public 1 0 0 0
6 access_product_pricelist_version_public product.pricelist.version.public product.model_product_pricelist_version base.group_public 1 0 0 0
7 access_product_pricelist_public product.pricelist.public product.model_product_pricelist base.group_public 1 0 0 0
8 access_product_product_price_type_public product.price.type.public product.model_product_price_type base.group_public 1 0 0 0
9 access_sale_order_public sale.order.public model_sale_order base.group_public 1 0 0 0
10 access_sale_order_line_public sale.order.line.public model_sale_order_line base.group_public 1 0 0 0
11 access_product_attribute product.attribute.public website_sale.model_product_attribute base.group_public 1 0 0 0
12 access_product_attribute_value product.attribute.value.public website_sale.model_product_attribute_value base.group_public 1 0 0 0
13 access_product_attribute_product product.attribute.product.public website_sale.model_product_attribute_product base.group_public 1 0 0 0

View File

@ -42,5 +42,16 @@
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
<record id="product_pricelist_public" model="ir.rule">
<field name="name">Public product pricelist</field>
<field name="model_id" ref="product.model_product_pricelist"/>
<field name="domain_force">[('id','=',session.get('ecommerce_pricelist'))]</field>
<field name="groups" eval="[(4, ref('base.group_public'))]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
</data>
</openerp>
</openerp>

View File

@ -133,7 +133,7 @@
height: 560px;
}
@media (max-width: 600px) {
@media (max-width: 768px) {
#products_grid table, #products_grid tbody, #products_grid tr, #products_grid td {
float: left;
width: 100%;
@ -145,6 +145,16 @@
height: 300px;
display: inline-block;
}
.products_pager {
text-align: center;
margin: 10px 0;
}
.products_pager .pagination {
float: none !important;
margin: 0 !important;
padding: 0 !important;
}
}
@media (max-width: 400px) {
#products_grid .oe_product {

View File

@ -121,7 +121,7 @@
.oe-height-8
height: 560px
@media (max-width: 600px)
@media (max-width: 768px)
#products_grid
table, tbody,tr, td
float: left
@ -132,6 +132,13 @@
width: 100%
height: 300px
display: inline-block
.products_pager
text-align: center
margin: 10px 0
.pagination
float: none !important
margin: 0 !important
padding: 0 !important
@media (max-width: 400px)
#products_grid

View File

@ -17,7 +17,7 @@ $(document).ready(function () {
function set_my_cart_quantity(qty) {
var $q = $(".my_cart_quantity");
$q.parent().parent().toggleClass("hidden", !qty);
$q.parent().parent().removeClass("hidden", !qty);
$q.html(qty)
.hide()
.fadeIn(600);
@ -43,13 +43,21 @@ $(document).ready(function () {
var $link = $(ev.currentTarget);
var product = $link.attr("href").match(/product_id=([0-9]+)/);
var product_id = product ? +product[1] : 0;
openerp.jsonRpc("/shop/add_cart_json/", 'call', {'product_id': product_id, 'order_line_id': $link.data('id'), 'remove': $link.is('[href*="/remove_cart/"]')})
if (!product) {
var line = $link.attr("href").match(/order_line_id=([0-9]+)/);
order_line_id = line ? +line[1] : 0;
}
openerp.jsonRpc("/shop/add_cart_json/", 'call', {
'product_id': product_id,
'order_line_id': order_line_id,
'remove': $link.is('[href*="remove"]')})
.then(function (data) {
if (!data[0]) {
location.reload();
}
set_my_cart_quantity(data[1]);
$link.parents(".input-group:first").find(".js_quantity").val(data[0]);
$('[data-oe-model="sale.order"][data-oe-field="amount_total"]').replaceWith(data[3]);
});
return false;
});

View File

@ -4,9 +4,8 @@
<!-- Layout add nav and footer -->
<template id="header" inherit_id="website.layout" name="Custom Header">
<template id="header" inherit_id="website.layout" name="Header Shop My Cart Link">
<xpath expr="//header//ul[@id='top_menu']/li" position="before">
<li><a t-href="/shop/">Shop</a></li>
<li t-att-class="(not website_sale_order or not website_sale_order.get_total_quantity()) and 'hidden' or ''">
<a t-href="/shop/mycart/">
<i class="icon-shopping-cart"></i>
@ -20,7 +19,7 @@
<!-- List of categories -->
<template id="categories_recursive" name="Category list">
<li t-att-class="category.id == search.get('category') and 'active' or ''">
<li t-att-class="str(category.id) == search.get('category') and 'active' or ''">
<a t-att-class="category.id not in categ[1] and 'unpublish' or ''" t-href="/shop/?category=#{ category.id }" t-field="category.name" t-keep-query="search,facettes"></a>
<ul t-if="category.child_id" class="nav nav-pills nav-stacked nav-hierarchy">
<t t-foreach="category.child_id" t-as="category">
@ -34,6 +33,12 @@
<!-- Product list -->
<template id="search" name="Search hidden fields">
<input type="hidden" name="category" t-att-value="search.get('category') or ''"/>
<input type="hidden" name="filter" t-att-value="search.get('filter') or ''"/>
<input type="text" name="search" class="search-query form-control" placeholder="Search..." t-att-value="search.get('search') or ''"/>
</template>
<template id="products_cart" name="Shopping cart">
<div class="ribbon-wrapper">
<div class="ribbon">Promo</div>
@ -65,9 +70,11 @@
</div>
</template>
<template id="products" name="Products" page="True">
<template id="products" name="Products">
<t t-call="website.layout">
<t t-set="head">
<script type="text/javascript" src="/web/static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js"></script>
<link rel='stylesheet' href="/web/static/lib/jquery.ui/css/smoothness/jquery-ui-1.9.1.custom.css"/>
<script type="text/javascript" src="/website_sale/static/src/js/website_sale.js"></script>
<link rel='stylesheet' href='/website_sale/static/src/css/website_sale.css'/>
<t t-raw="head or ''"/>
@ -77,23 +84,27 @@
<div class="oe_structure"/>
<div class="container oe_website_sale">
<div class="row">
<div class="col-sm-6 pagination" style="padding-left: 15px;">
<a t-if="editable" t-href="/shop/add_product/" class="btn btn-primary btn-default" t-keep-query="category,search,facettes">New Product</a>
<div class="col-sm-6 pagination hidden-xs" style="padding-left: 15px;">
<form t-if="editable" t-keep-query="category,search,facettes"
method="POST" t-action="/shop/add_product">
<button class="btn btn-primary">New Product</button>
</form>
</div>
<div class="col-sm-6">
<div class="col-sm-6 products_pager">
<t t-call="website.pager">
<t t-set="classname">pull-right</t>
<t t-set="style">padding-left: 5px;</t>
</t>
<form t-action="/shop/" method="get" class="pull-right pagination form-inline" style="padding-right: 5px;" t-keep-query="category,search,facettes">
<div class="form-group">
<input type="text" name="search" class="search-query form-control" placeholder="Search..." t-att-value="search.get('search') or ''"/>
</div>
<form t-action="/shop/" method="get" class="pull-right pagination form-inline" style="padding-right: 5px;">
<t t-call="website_sale.search" />
</form>
</div>
</div>
<div class='style_default row'>
<div class="hidden" id="products_grid_before"></div>
<div class="col-md-12" id="products_grid">
<t t-if="product_ids">
<table width="100%">
<tbody>
<t t-set="table_products" t-value="Ecommerce.get_bin_packing_products(product_ids, product_ids_for_holes, 4)"/>
@ -167,9 +178,13 @@
</tr>
</tbody>
</table>
</t>
<t t-if="not product_ids">
<h3 class="text-center text-muted">No product found for this search</h3>
</t>
</div>
</div>
<div>
<div class="products_pager">
<t t-call="website.pager">
<t t-set="classname">pull-right</t>
</t>
@ -231,7 +246,7 @@
<div class="col-sm-5">
<ol class="breadcrumb">
<li><a t-href="/shop">Products</a></li>
<li t-if="search.get('category')"><a t-att-href="'/shop/" t-keep-query="category,search,facettes"><span t-field="category.name"/></a></li>
<li t-if="search.get('category')"><a t-href="/shop/" t-keep-query="category,search,facettes"><span t-field="category.name"/></a></li>
<li class="active"><span t-field="product.name"></span></li>
</ol>
</div><div class="col-sm-3">
@ -247,17 +262,9 @@
</li>
</t>
</div><div class="col-sm-3 col-sm-offset-1">
<form t-action="/shop/" method="get" class="pull-right" t-keep-query="category,facettes">
<div class="input-group">
<t t-if="search">
<t foreach="search.items()" t-as="key">
<input t-att-name="key[0]" t-att-value="key[1]"/>
</t>
</t>
<span class="input-group-addon"><span class="glyphicon glyphicon-search"/></span>
<input type="text" name="search" class="search-query form-control" placeholder="Search..." t-att-value="search or ''"/>
</div>
</form>
<form t-action="/shop/" method="get" class="pull-right">
<t t-call="website_sale.search" />
</form>
</div>
</div>
</section>
@ -265,7 +272,7 @@
<section class="container oe_website_sale mb16" id="product_detail">
<div class="row">
<div class="col-sm-7 col-md-7 col-lg-7">
<span t-field="product.image" style="max-height: 500px" t-field-options='{"widget": "image"}'/>
<span t-field="product.image" style="max-height: 500px" t-field-options='{"widget": "image", "class": "img img-responsive"}'/>
</div><div class="col-sm-5 col-md-5 col-lg-4 col-lg-offset-1">
<h1 t-field="product.name">Product Name</h1>
@ -338,9 +345,19 @@
</xpath>
</template>
<template id="product_attributes" inherit_option_id="website_sale.product" name="Product Attributes">
<xpath expr="//p[@t-field='product.description_sale']" position="after">
<hr t-if="product.website_attribute_ids"/>
<p class="text-muted">
<t t-set="attr" t-value="None"/>
<t t-foreach="product.website_attribute_ids" t-as="attribute"><br t-if="attr and attribute.attribute_id.id != attr"/><t t-if="attribute.attribute_id.id != attr"><span t-field="attribute.attribute_id"/>: </t><t t-if="attribute.attribute_id.id == attr">, </t><t t-if="attribute.attribute_id.type == 'distinct'"><span t-field="attribute.value_id"/></t><t t-if="attribute.attribute_id.type == 'float'"><span t-field="attribute.value"/></t><t t-set="attr" t-value="attribute.attribute_id.id"/></t>
</p>
</xpath>
</template>
<!-- Page Shop my cart -->
<template id="mycart" name="Your Cart" page="True">
<template id="mycart" name="Your Cart">
<t t-call="website.layout">
<t t-set="head">
<script type="text/javascript" src="/website_sale/static/src/js/website_sale.js"></script>
@ -358,18 +375,6 @@
</ul>
<h1 class="mb32">Shopping Cart</h1>
<div class="row">
<div class="col-md-3 text-muted" id="right_column">
<h4>Policies</h4>
<ul class="list-unstyled mb32">
<li>&#9745; 30-days money-back guarantee</li>
<li>&#9745; Invoice sent by e-Mail</li>
</ul>
<h4>Secure Payment</h4>
<ul class="list-unstyled mb32">
<li>&#9745; 256 bit encryption</li>
<li>&#9745; Processed by Ogone</li>
</ul>
</div>
<div class="col-md-8 col-md-offset-1 oe_mycart">
<div t-if="not website_sale_order or not website_sale_order.order_line" class="well well-lg">
Your cart is empty!
@ -418,14 +423,14 @@
<td>
<div class="input-group">
<span class="input-group-addon">
<a t-href="./remove_cart/?order_line_id=#{ line.id }" t-att-data-id="line.id" class="mb8 js_add_cart_json">
<a t-href="./add_cart/?remove=True&amp;order_line_id=#{ line.id }" class="mb8 js_add_cart_json">
<span class="icon-minus"/>
</a>
</span>
<input type="text" class="js_quantity form-control"
t-att-data-id="line.id" t-att-value="int(line.product_uom_qty)"/>
<span class="input-group-addon">
<a t-href="./add_cart/?order_line_id=#{ line.id }" t-att-data-id="line.id" class="mb8 float_left js_add_cart_json">
<a t-href="./add_cart/?order_line_id=#{ line.id }" class="mb8 float_left js_add_cart_json">
<span class="icon-plus"/>
</a>
</span>
@ -435,7 +440,7 @@
</tr>
</tbody>
</table>
<table class='pull-right mb16' id="mycart_total">
<table class='pull-right mb16' id="mycart_total" t-if="website_sale_order">
<colgroup>
<col width="100"/>
<col width="120"/>
@ -443,11 +448,8 @@
<thead>
<tr style="border-top: 1px solid #000">
<th><h3>Total:</h3></th>
<th class="text-right"><h3>
<span t-field="website_sale_order.amount_total" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/></h3>
<th class="text-right">
<h3><t t-call="website_sale.total"/></h3>
</th>
</tr>
<tr class="text-muted">
@ -466,6 +468,18 @@
<a t-if="website_sale_order and website_sale_order.order_line" t-href="/shop/checkout/" class="btn btn-primary pull-right mb32">Process Checkout <span class="icon-long-arrow-right"/></a>
<div class="oe_structure"/>
</div>
<div class="col-md-3 text-muted" id="right_column">
<h4>Policies</h4>
<ul class="list-unstyled mb32">
<li>&#9745; 30-days money-back guarantee</li>
<li>&#9745; Invoice sent by e-Mail</li>
</ul>
<h4>Secure Payment</h4>
<ul class="list-unstyled mb32">
<li>&#9745; 256 bit encryption</li>
<li>&#9745; Processed by Ogone</li>
</ul>
</div>
</div>
</div>
@ -477,36 +491,38 @@
<!-- Page Shop -->
<template id="products_categories" inherit_option_id="website_sale.products" name="Product Categories">
<xpath expr="//div[@id='products_grid']" position="before">
<div id="categories" class="col-md-3">
<ul class="nav nav-pills nav-stacked mt16">
<li t-att-class=" '' if search.get('category') else 'active' "><a t-href="/shop/">All Products</a></li>
<t t-set="categ" t-value="Ecommerce.get_categories()"/>
<t t-foreach="categ[0]" t-as="category">
<t t-call="website_sale.categories_recursive"/>
</t>
</ul>
</div>
<xpath expr="//div[@id='products_grid_before']" position="inside">
<ul class="nav nav-pills nav-stacked mt16">
<li t-att-class=" '' if search.get('category') else 'active' "><a t-href="/shop/">All Products</a></li>
<t t-set="categ" t-value="Ecommerce.get_categories()"/>
<t t-foreach="categ[0]" t-as="category">
<t t-call="website_sale.categories_recursive"/>
</t>
</ul>
</xpath>
<xpath expr="//div[@id='products_grid_before']" position="attributes">
<attribute name="class">col-md-3</attribute>
</xpath>
<xpath expr="//div[@id='products_grid']" position="attributes">
<attribute name="class">col-md-9</attribute>
</xpath>
</template>
<template id="products_attributes" inherit_option_id="website_sale.products_categories" name="Product Attributes">
<xpath expr="//div[@id='categories']" position="inside">
<form t-action="/shop/attributes/" method="post" t-keep-query="category,search">
<template id="products_attributes" inherit_option_id="website_sale.products" name="Product Filters and Attributes">
<xpath expr="//div[@id='products_grid_before']" position="inside">
<form t-action="/shop/filter/" method="post" t-keep-query="category,search,add_filter">
<ul class="nav nav-pills nav-stacked mt16">
<t t-set="attribute_ids" t-value="Ecommerce.get_attribute_ids()"/>
<t t-foreach="attribute_ids" t-as="attribute_id">
<t t-if="attribute_id.visible">
<li t-if="attribute_id.value_ids and attribute_id.type == 'distinct'">
<div t-field="attribute_id.name"/>
<ul class="nav nav-pills nav-stacked">
<t t-foreach="attribute_id.value_ids" t-as="value_id">
<li t-att-class="Ecommerce.has_search_attributes(attribute_id.id, value_id.id) and 'active' or ''">
<li t-att-class="Ecommerce.has_search_filter(attribute_id.id, value_id.id) and 'active' or ''">
<label style="margin: 0 20px;">
<input type="checkbox" t-att-name="'att-%s-%s' % (attribute_id.id, value_id.id)"
t-att-checked="Ecommerce.has_search_attributes(attribute_id.id, value_id.id) and 'checked' or ''"/>
t-att-checked="Ecommerce.has_search_filter(attribute_id.id, value_id.id) and 'checked' or ''"/>
<span style="font-weight: normal" t-field="value_id.name"/>
</label>
</li>
@ -515,7 +531,7 @@
</li>
<li t-if="attribute_id.type == 'float' and attribute_id.float_min != attribute_id.float_max">
<div t-field="attribute_id.name"/>
<t t-set="attribute" t-value="Ecommerce.has_search_attributes(attribute_id.id)"/>
<t t-set="attribute" t-value="Ecommerce.has_search_filter(attribute_id.id)"/>
<div style="margin: 0 20px;" class="js_slider"
t-att-data-id="attribute_id.id"
t-att-data-value-min="attribute and attribute[1][0] or attribute_id.float_min"
@ -523,14 +539,22 @@
t-att-data-min="attribute_id.float_min"
t-att-data-max="attribute_id.float_max"></div>
</li>
</t>
</t>
</ul>
<button class="btn btn-xs btn-primary mt16">Apply filter</button>
<a t-href="/shop/" t-keep-query="category,search,add_filter" class="btn btn-xs btn-default mt16">Cancel filter</a>
</form>
</xpath>
<xpath expr="//div[@id='products_grid_before']" position="attributes">
<attribute name="class">col-md-3</attribute>
</xpath>
<xpath expr="//div[@id='products_grid']" position="attributes">
<attribute name="class">col-md-9</attribute>
</xpath>
</template>
<template id="suggested_products_list" inherit_id="website_sale.mycart" inherit_option_id="website_sale.mycart" name="Suggested Products in list view">
<template id="suggested_products_list" inherit_id="website_sale.mycart" inherit_option_id="website_sale.mycart" name="Suggested Products in my cart">
<xpath expr="//table[@id='mycart_products']" position="after">
<table t-if="suggested_products" class='table table-striped table-condensed'>
<colgroup>
@ -619,7 +643,7 @@
<div class="col-md-8 oe_mycart">
<h3 class="page-header mt16">Billing Information
<small t-if="user_id.id == website.public_user.id"> or
<a t-if="not partner" t-attf-href="/web#action=redirect&amp;url=#{ request.httprequest.host_url }/shop/checkout/">sign in</a>
<a t-if="not partner" t-attf-href="/web#action=redirect&amp;url=#{ request.httprequest.url }">sign in</a>
</small>
</h3>
<div class="row">
@ -636,7 +660,7 @@
<input type="email" name="email" class="form-control" t-att-value="checkout.get('email')"/>
</div>
<div t-attf-class="form-group #{ error.get('phone') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="phone">Telephone</label>
<label class="control-label" for="phone">Phone</label>
<input type="tel" name="phone" class="form-control" t-att-value="checkout.get('phone')"/>
</div>
@ -692,7 +716,7 @@
<input type="text" name="shipping_name" class="form-control" t-att-value="checkout.get('shipping_name', '')"/>
</div>
<div t-attf-class="form-group #{error.get('shipping_phone') and 'has-error' or ''} col-lg-6">
<label class="control-label" for="contact_name">Telephone</label>
<label class="control-label" for="contact_name">Phone</label>
<input type="tel" name="shipping_phone" class="form-control" t-att-value="checkout.get('shipping_phone', '')"/>
</div>
<div t-attf-class="form-group #{error.get('shipping_street') and 'has-error' or ''} col-lg-6">
@ -876,5 +900,12 @@
</t>
</template>
<template id="total">
<span t-field="website_sale_order.amount_total" t-field-options='{
"widget": "monetary",
"display_currency": "website.pricelist_id.currency_id"
}'/>
</template>
</data>
</openerp>

View File

@ -64,7 +64,7 @@
<field name="website_published"/>
</xpath>
<xpath expr="//page[@string='Information']" position="inside">
<group colspan="4" string="Products On Ecommerce">
<group colspan="4" string="Website Options">
<field name="suggested_product_ids" widget="many2many_tags"/>
<field name="website_style_ids" widget="many2many_tags"/>
<field colspan="4" name="website_attribute_ids" nolabel="1">
@ -81,9 +81,22 @@
</group>
</xpath>
</field>
</record>
<record model="ir.ui.view" id="view_product_attribute_form">
<field name="name">product.attribute.form</field>
<field name="model">product.attribute</field>
<field name="arch" type="xml">
<form string="Product Attributes" version="7.0">
<group>
<field name="name"/>
<field name="type"/>
<field name="visible"/>
</group>
</form>
</field>
</record>
</data>
</openerp>

View File

@ -2,6 +2,12 @@
<openerp>
<data noupdate="1">
<record id="menu_shop" model="website.menu">
<field name="name">Shop</field>
<field name="url">/shop</field>
<field name="parent_id" ref="website.main_menu"/>
<field name="sequence" type="int">30</field>
</record>
<record id="action_open_website" model="ir.actions.act_url">
<field name="name">Website Shop</field>
<field name="target">self</field>