From 1baf52ac6eb7f3c434483e83b4c862fb8419d76e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Mon, 2 Oct 2017 23:53:55 +0000 Subject: [PATCH 01/44] =?UTF-8?q?R=C3=A9cup=C3=A8re=20les=20associations?= =?UTF-8?q?=20machine=5Ftype->ip=20pour=20le=20template?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- machines/views.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/machines/views.py b/machines/views.py index 4268519b..aa702438 100644 --- a/machines/views.py +++ b/machines/views.py @@ -117,7 +117,10 @@ def new_machine(request, userid): reversion.set_comment("Création") messages.success(request, "La machine a été créée") return redirect("/users/profil/" + str(user.id)) - return form({'machineform': machine, 'interfaceform': interface, 'domainform': domain}, 'machines/machine.html', request) + type_to_ipv4 = {} + for t in interface.fields['type'].queryset : + type_to_ipv4[str(t.id)] = IpList.objects.filter(interface__isnull=True).filter(ip_type=t.ip_type) + return form({'machineform': machine, 'interfaceform': interface, 'domainform': domain, 'type_to_ipv4': type_to_ipv4}, 'machines/machine.html', request) @login_required def edit_interface(request, interfaceid): From d890d4ac1e4fe9ff627747156a0641c5969d1d69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Mon, 2 Oct 2017 23:54:53 +0000 Subject: [PATCH 02/44] =?UTF-8?q?G=C3=A8re=20l'affichage=20dynamique=20des?= =?UTF-8?q?=20choix=20d'ip=20en=20fonction=20du=20machine=5Ftype=20Enl?= =?UTF-8?q?=C3=A8ve=20toutes=20les=20options=20au=20loading=20de=20la=20pa?= =?UTF-8?q?ge=20si=20JS=20activ=C3=A9=20Ajoute=20des=20options=20quand=20l?= =?UTF-8?q?e=20machine=5Ftype=20change=20gr=C3=A2ce=20aux=20associations?= =?UTF-8?q?=20machien=5Ftype=20->=20ip=20re=C3=A7ues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- machines/templates/machines/machine.html | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/machines/templates/machines/machine.html b/machines/templates/machines/machine.html index 31a15679..85e51d86 100644 --- a/machines/templates/machines/machine.html +++ b/machines/templates/machines/machine.html @@ -38,6 +38,30 @@ with this program; if not, write to the Free Software Foundation, Inc., {% bootstrap_form_errors domainform %} {% endif %} +
{% csrf_token %} From 9bf0529cf7f0cb645f2c3fc137125fa74f07227f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Mon, 2 Oct 2017 23:56:40 +0000 Subject: [PATCH 03/44] =?UTF-8?q?Place=20le=20machine=5Ftype=20en=20premie?= =?UTF-8?q?r=20dans=20le=20formulaire=20Plus=20logique=20comme=20=C3=A7a?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- machines/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machines/forms.py b/machines/forms.py index 0de53c49..44e80c25 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -65,7 +65,7 @@ class EditInterfaceForm(ModelForm): class AddInterfaceForm(EditInterfaceForm): class Meta(EditInterfaceForm.Meta): - fields = ['ipv4','mac_address','type','details'] + fields = ['type','ipv4','mac_address','details'] def __init__(self, *args, **kwargs): infra = kwargs.pop('infra') From 063e964f5a2ef202b87d078ccfda170aa416a7d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Tue, 3 Oct 2017 00:07:53 +0000 Subject: [PATCH 04/44] Ajoute le JS que si la liste d'association existe Evite de casser les form existants --- machines/templates/machines/machine.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/machines/templates/machines/machine.html b/machines/templates/machines/machine.html index 85e51d86..2032a078 100644 --- a/machines/templates/machines/machine.html +++ b/machines/templates/machines/machine.html @@ -38,6 +38,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% bootstrap_form_errors domainform %} {% endif %} +{% if type_to_ipv4 %} +{% endif %} {% csrf_token %} From f661e00c6c977f91e2b70b11d890f20b3d21a902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Tue, 3 Oct 2017 00:08:28 +0000 Subject: [PATCH 05/44] Ajoute le JS sur d'autres form que le new_machine --- machines/views.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/machines/views.py b/machines/views.py index aa702438..d1feb8df 100644 --- a/machines/views.py +++ b/machines/views.py @@ -157,7 +157,10 @@ def edit_interface(request, interfaceid): reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in domain_form.changed_data)) messages.success(request, "La machine a été modifiée") return redirect("/users/profil/" + str(interface.machine.user.id)) - return form({'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form}, 'machines/machine.html', request) + type_to_ipv4 = {} + for t in interface_form.fields['type'].queryset : + type_to_ipv4[str(t.id)] = IpList.objects.filter(interface__isnull=True).filter(ip_type=t.ip_type) + return form({'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form, 'type_to_ipv4': type_to_ipv4}, 'machines/machine.html', request) @login_required def del_machine(request, machineid): @@ -213,7 +216,10 @@ def new_interface(request, machineid): reversion.set_comment("Création") messages.success(request, "L'interface a été ajoutée") return redirect("/users/profil/" + str(machine.user.id)) - return form({'interfaceform': interface_form, 'domainform': domain_form}, 'machines/machine.html', request) + type_to_ipv4 = {} + for t in interface_form.fields['type'].queryset : + type_to_ipv4[str(t.id)] = IpList.objects.filter(interface__isnull=True).filter(ip_type=t.ip_type) + return form({'interfaceform': interface_form, 'domainform': domain_form, 'type_to_ipv4': type_to_ipv4}, 'machines/machine.html', request) @login_required def del_interface(request, interfaceid): From cfecb157931193ae6fc9ae55191d1d80cb8e1c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Tue, 3 Oct 2017 02:09:41 +0000 Subject: [PATCH 06/44] =?UTF-8?q?Optimisation,=20essaye=20d'acc=C3=A9l?= =?UTF-8?q?=C3=A9rer=20la=20cr=C3=A9ation=20du=20form=20d'interface=20Long?= =?UTF-8?q?=20pour=20beaucoup=20d'ip=20(5-15=20sec=20pour=20un=20/16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- machines/templates/machines/machine.html | 3 +-- machines/views.py | 9 ++++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/machines/templates/machines/machine.html b/machines/templates/machines/machine.html index 2032a078..1e753f6f 100644 --- a/machines/templates/machines/machine.html +++ b/machines/templates/machines/machine.html @@ -43,8 +43,7 @@ with this program; if not, write to the Free Software Foundation, Inc., var type_to_ipv4 = { type: [{% for type, iplist in type_to_ipv4.items %}"{{ type }}",{% endfor %}], iplist: [{% for type, iplist in type_to_ipv4.items %} - [{% for ip in iplist %} {id:"{{ ip.id }}", ip:"{{ ip.ipv4 }}"}, - {% endfor %}], {% endfor %} + [ {{ iplist | safe }} ], {% endfor %} ] } $(document).ready(function() { diff --git a/machines/views.py b/machines/views.py index d1feb8df..951671c4 100644 --- a/machines/views.py +++ b/machines/views.py @@ -119,7 +119,8 @@ def new_machine(request, userid): return redirect("/users/profil/" + str(user.id)) type_to_ipv4 = {} for t in interface.fields['type'].queryset : - type_to_ipv4[str(t.id)] = IpList.objects.filter(interface__isnull=True).filter(ip_type=t.ip_type) + iplist = IpList.objects.filter(interface__isnull=True).filter(ip_type=t.ip_type) + type_to_ipv4[str(t.id)] = ','.join(['{id:"%s", ip:"%s"}' % (b.id, b.ipv4) for b in iplist]) return form({'machineform': machine, 'interfaceform': interface, 'domainform': domain, 'type_to_ipv4': type_to_ipv4}, 'machines/machine.html', request) @login_required @@ -159,7 +160,8 @@ def edit_interface(request, interfaceid): return redirect("/users/profil/" + str(interface.machine.user.id)) type_to_ipv4 = {} for t in interface_form.fields['type'].queryset : - type_to_ipv4[str(t.id)] = IpList.objects.filter(interface__isnull=True).filter(ip_type=t.ip_type) + iplist = IpList.objects.filter(interface__isnull=True).filter(ip_type=t.ip_type) + type_to_ipv4[str(t.id)] = ','.join(['{id:"%s", ip:"%s"}' % (b.id, b.ipv4) for b in iplist]) return form({'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form, 'type_to_ipv4': type_to_ipv4}, 'machines/machine.html', request) @login_required @@ -218,7 +220,8 @@ def new_interface(request, machineid): return redirect("/users/profil/" + str(machine.user.id)) type_to_ipv4 = {} for t in interface_form.fields['type'].queryset : - type_to_ipv4[str(t.id)] = IpList.objects.filter(interface__isnull=True).filter(ip_type=t.ip_type) + iplist = IpList.objects.filter(interface__isnull=True).filter(ip_type=t.ip_type) + type_to_ipv4[str(t.id)] = ','.join(['{id:"%s", ip:"%s"}' % (b.id, b.ipv4) for b in iplist]) return form({'interfaceform': interface_form, 'domainform': domain_form, 'type_to_ipv4': type_to_ipv4}, 'machines/machine.html', request) @login_required From 33f1a21a0a9ee5f3301dd381e2cc390687fd3922 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Thu, 5 Oct 2017 03:21:36 +0200 Subject: [PATCH 07/44] Doc on models machines --- machines/models.py | 55 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/machines/models.py b/machines/models.py index 8e7b1c3c..698fdb90 100644 --- a/machines/models.py +++ b/machines/models.py @@ -56,6 +56,7 @@ class MachineType(models.Model): ip_type = models.ForeignKey('IpType', on_delete=models.PROTECT, blank=True, null=True) def all_interfaces(self): + """ Renvoie toutes les interfaces (cartes réseaux) de type machinetype""" return Interface.objects.filter(type=self) def __str__(self): @@ -75,23 +76,31 @@ class IpType(models.Model): @cached_property def ip_range(self): - return IPRange(self.domaine_ip_start, end=self.domaine_ip_stop) + """ Renvoie un objet IPRange à partir de l'objet IpType""" + return IPRange(self.domaine_ip_start, end=self.domaine_ip_stop) @cached_property def ip_set(self): + """ Renvoie une IPSet à partir de l'iptype""" return IPSet(self.ip_range) @cached_property def ip_set_as_str(self): + """ Renvoie une liste des ip en string""" return [str(x) for x in self.ip_set] def ip_objects(self): + """ Renvoie tous les objets ipv4 relié à ce type""" return IpList.objects.filter(ip_type=self) def free_ip(self): + """ Renvoie toutes les ip libres associées au type donné (self)""" return IpList.objects.filter(interface__isnull=True).filter(ip_type=self) def gen_ip_range(self): + """ Cree les IpList associées au type self. Parcours pédestrement et crée + les ip une par une. Si elles existent déjà, met à jour le type associé + à l'ip""" # Creation du range d'ip dans les objets iplist networks = [] for net in self.ip_range.cidrs(): @@ -114,6 +123,11 @@ class IpType(models.Model): ip.delete() def clean(self): + """ Nettoyage. Vérifie : + - Que ip_stop est après ip_start + - Qu'on ne crée pas plus gros qu'un /16 + - Que le range crée ne recoupe pas un range existant + - Formate l'ipv6 donnée en /64""" if IPAddress(self.domaine_ip_start) > IPAddress(self.domaine_ip_stop): raise ValidationError("Domaine end doit être après start...") # On ne crée pas plus grand qu'un /16 @@ -136,6 +150,7 @@ class IpType(models.Model): return self.type class Vlan(models.Model): + """ Un vlan : vlan_id et nom""" PRETTY_NAME = "Vlans" vlan_id = models.IntegerField() @@ -146,6 +161,9 @@ class Vlan(models.Model): return self.name class Nas(models.Model): + """ Les nas. Associé à un machine_type. + Permet aussi de régler le port_access_mode (802.1X ou mac-address) pour + le radius. Champ autocapture de la mac à true ou false""" PRETTY_NAME = "Correspondance entre les nas et les machines connectées" default_mode = '802.1X' @@ -164,6 +182,8 @@ class Nas(models.Model): return self.name class Extension(models.Model): + """ Extension dns type example.org. Précise si tout le monde peut l'utiliser, + associé à un origin (ip d'origine)""" PRETTY_NAME = "Extensions dns" name = models.CharField(max_length=255, unique=True) @@ -172,12 +192,15 @@ class Extension(models.Model): @cached_property def dns_entry(self): + """ Une entrée DNS A""" return "@ IN A " + str(self.origin) def __str__(self): return self.name class Mx(models.Model): + """ Entrées des MX. Enregistre la zone (extension) associée et la priorité + Todo : pouvoir associer un MX à une interface """ PRETTY_NAME = "Enregistrements MX" zone = models.ForeignKey('Extension', on_delete=models.PROTECT) @@ -205,6 +228,7 @@ class Ns(models.Model): return str(self.zone) + ' ' + str(self.ns) class Text(models.Model): + """ Un enregistrement TXT associé à une extension""" PRETTY_NAME = "Enregistrement text" zone = models.ForeignKey('Extension', on_delete=models.PROTECT) @@ -219,6 +243,12 @@ class Text(models.Model): return str(self.field1) + " IN TXT " + str(self.field2) class Interface(models.Model): + """ Une interface. Objet clef de l'application machine : + - une address mac unique. Possibilité de la rendre unique avec le typemachine + - une onetoone vers IpList pour attribution ipv4 + - le type parent associé au range ip et à l'extension + - un objet domain associé contenant son nom + - la liste des ports oiuvert""" PRETTY_NAME = "Interface" ipv4 = models.OneToOneField('IpList', on_delete=models.PROTECT, blank=True, null=True) @@ -238,6 +268,7 @@ class Interface(models.Model): @cached_property def ipv6_object(self): + """ Renvoie un objet type ipv6 à partir du prefix associé à l'iptype parent""" if self.type.ip_type.prefix_v6: return EUI(self.mac_address).ipv6(IPNetwork(self.type.ip_type.prefix_v6).network) else: @@ -245,18 +276,23 @@ class Interface(models.Model): @cached_property def ipv6(self): + """ Renvoie l'ipv6 en str. Mise en cache et propriété de l'objet""" return str(self.ipv6_object) def mac_bare(self): + """ Formatage de la mac type mac_bare""" return str(EUI(self.mac_address, dialect=mac_bare)).lower() def filter_macaddress(self): + """ Tente un formatage mac_bare, si échoue, lève une erreur de validation""" try: self.mac_address = str(EUI(self.mac_address)) except : raise ValidationError("La mac donnée est invalide") def clean(self, *args, **kwargs): + """ Formate l'addresse mac en mac_bare (fonction filter_mac) + et assigne une ipv4 dans le bon range si inexistante ou incohérente""" self.filter_macaddress() self.mac_address = str(EUI(self.mac_address)) or None if not self.ipv4 or self.type.ip_type != self.ipv4.ip_type: @@ -273,6 +309,7 @@ class Interface(models.Model): return def unassign_ipv4(self): + """ Sans commentaire, désassigne une ipv4""" self.ipv4 = None def update_type(self): @@ -295,15 +332,20 @@ class Interface(models.Model): return str(domain) def has_private_ip(self): + """ True si l'ip associée est privée""" if self.ipv4: return IPAddress(str(self.ipv4)).is_private() else: return False def may_have_port_open(self): + """ True si l'interface a une ip et une ip publique. + Permet de ne pas exporter des ouvertures sur des ip privées (useless)""" return self.ipv4 and not self.has_private_ip() class Domain(models.Model): + """ Objet domain. Enregistrement A et CNAME en même temps : permet de stocker les + alias et les nom de machines, suivant si interface_parent ou cname sont remplis""" PRETTY_NAME = "Domaine dns" interface_parent = models.OneToOneField('Interface', on_delete=models.CASCADE, blank=True, null=True) @@ -315,6 +357,8 @@ class Domain(models.Model): unique_together = (("name", "extension"),) def get_extension(self): + """ Retourne l'extension de l'interface parente si c'est un A + Retourne l'extension propre si c'est un cname, renvoie None sinon""" if self.interface_parent: return self.interface_parent.type.ip_type.extension elif hasattr(self,'extension'): @@ -323,6 +367,11 @@ class Domain(models.Model): return None def clean(self): + """ Validation : + - l'objet est bien soit A soit CNAME + - le cname est pas pointé sur lui-même + - le nom contient bien les caractères autorisés par la norme dns et moins de 63 caractères au total + - le couple nom/extension est bien unique""" if self.get_extension(): self.extension=self.get_extension() """ Validation du nom de domaine, extensions dans type de machine, prefixe pas plus long que 63 caractères """ @@ -341,10 +390,12 @@ class Domain(models.Model): @cached_property def dns_entry(self): + """ Une entrée DNS""" if self.cname: return str(self.name) + " IN CNAME " + str(self.cname) + "." def save(self, *args, **kwargs): + """ Empèche le save sans extension valide. Force à avoir appellé clean avant""" if not self.get_extension(): raise ValidationError("Extension invalide") self.full_clean() @@ -361,9 +412,11 @@ class IpList(models.Model): @cached_property def need_infra(self): + """ Permet de savoir si un user basique peut assigner cette ip ou non""" return self.ip_type.need_infra def clean(self): + """ Erreur si l'ip_type est incorrect""" if not str(self.ipv4) in self.ip_type.ip_set_as_str: raise ValidationError("L'ipv4 et le range de l'iptype ne correspondent pas!") return From c4bdab82f069324035e884458dfbb929cca2fa3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Thu, 5 Oct 2017 20:51:06 +0000 Subject: [PATCH 08/44] =?UTF-8?q?Templatag=20pour=20g=C3=A9n=C3=A9rer=20de?= =?UTF-8?q?s=20form=20avec=20typeahead=20Utilise=20les=20form=20django=20e?= =?UTF-8?q?t=20la=20g=C3=A9n=C3=A9ration=20de=20bootstrap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- machines/templates/machines/machine.html | 29 +- machines/templatetags/__init__.py | 20 + .../templatetags/bootstrap_form_typeahead.py | 179 + machines/views.py | 18 +- static/css/typeaheadjs.css | 93 + static/js/handlebars.js | 4840 +++++++++++++++++ static/js/typeahead.js | 2451 +++++++++ templates/base.html | 3 + 8 files changed, 7591 insertions(+), 42 deletions(-) create mode 100644 machines/templatetags/__init__.py create mode 100644 machines/templatetags/bootstrap_form_typeahead.py create mode 100644 static/css/typeaheadjs.css create mode 100644 static/js/handlebars.js create mode 100644 static/js/typeahead.js diff --git a/machines/templates/machines/machine.html b/machines/templates/machines/machine.html index 1e753f6f..bc1ac30f 100644 --- a/machines/templates/machines/machine.html +++ b/machines/templates/machines/machine.html @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endcomment %} {% load bootstrap3 %} +{% load bootstrap_form_typeahead %} {% block title %}Création et modification de machines{% endblock %} @@ -38,39 +39,13 @@ with this program; if not, write to the Free Software Foundation, Inc., {% bootstrap_form_errors domainform %} {% endif %} -{% if type_to_ipv4 %} - -{% endif %} - {% csrf_token %} {% if machineform %} {% bootstrap_form machineform %} {% endif %} {% if interfaceform %} - {% bootstrap_form interfaceform %} + {% bootstrap_form_typeahead interfaceform 'ipv4,type' %} {% endif %} {% if domainform %} {% bootstrap_form domainform %} diff --git a/machines/templatetags/__init__.py b/machines/templatetags/__init__.py new file mode 100644 index 00000000..5e24b69e --- /dev/null +++ b/machines/templatetags/__init__.py @@ -0,0 +1,20 @@ +# Re2o est un logiciel d'administration développé initiallement au rezometz. Il +# se veut agnostique au réseau considéré, de manière à être installable en +# quelques clics. +# +# Copyright © 2017 Maël Kervella +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py new file mode 100644 index 00000000..9c1363df --- /dev/null +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -0,0 +1,179 @@ +# Re2o est un logiciel d'administration développé initiallement au rezometz. Il +# se veut agnostique au réseau considéré, de manière à être installable en +# quelques clics. +# +# Copyright © 2017 Maël Kervella +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from django import template +from django.utils.safestring import mark_safe +from bootstrap3.templatetags.bootstrap3 import bootstrap_form +from bootstrap3.utils import render_tag +from bootstrap3.forms import render_field + +register = template.Library() + +@register.simple_tag +def bootstrap_form_typeahead(django_form, typeahead_fields, *args, **kwargs): + """ + Render a form where some specific fields are rendered using Typeahead. + Using Typeahead really improves the performance, the speed and UX when + dealing with very large datasets (select with 50k+ elts for instance). + For convenience, it accepts the same parameters as a standard bootstrap + can accept. + + **Tag name**:: + + bootstrap_form_typeahead + + **Parameters**: + + form + The form that is to be rendered + + typeahead_fields + A list of field names (comma separated) that should be rendered + with typeahead instead of the default bootstrap renderer. + + See boostrap_form_ for other arguments + + **Usage**:: + + {% bootstrap_form_typeahead form ['field1[,field2[,...]]] %} + + **Example**: + + {% bootstrap_form_typeahead form 'ipv4' %} + """ + + t_fields = typeahead_fields.split(',') + exclude = kwargs.get('exclude', None) + exclude = exclude.split(',') if exclude else [] + + form = '' + for f_name, f_value in django_form.fields.items() : + if not f_name in exclude : + if f_name in t_fields : + form += render_tag( + 'div', + attrs = {'class': 'form-group'}, + content = label_tag( f_name, f_value ) + + input_tag( f_name, f_value ) + + hidden_tag( f_name ) + + typeahead_full_script( f_name, f_value ) + ) + + else: + form += render_field( + f_value.get_bound_field(django_form, f_name), + *args, + **kwargs + ) + + + return mark_safe( form ) + +def input_id( f_name ): + return 'typeahead_input_'+f_name + +def select_id( f_name ): + return 'typeahead_select_'+f_name + +def hidden_tag( f_name ): + return render_tag( + 'input', + attrs={ + 'id': select_id(f_name), + 'name': f_name, + 'type': 'hidden', + 'value': '' + } + ) + +def label_tag( f_name, f_value ): + return render_tag( + 'label', + attrs={ + 'class': 'control-label', + 'for': input_id(f_name) + }, + content=f_value.label + ) + +def input_tag( f_name, f_value ): + return render_tag( + 'input', + attrs={ + 'class': 'form-control', + 'id': input_id(f_name), + 'type': 'text', + 'placeholder': f_value.empty_label + }, + ) + +def typeahead_full_script( f_name, f_value ) : + js_content = \ + '$("#'+input_id(f_name)+'").ready( function() {\n' + \ + typeahead_choices( f_value ) + '\n' + \ + typeahead_engine () + '\n' + \ + '$("#'+input_id(f_name) + '").typeahead(\n' + \ + typeahead_datasets( f_name ) + \ + ').bind(\n' + \ + '"typeahead:select", ' + \ + typeahead_updater( f_name ) + '\n' + \ + ')\n' + \ + '});\n' + + return render_tag( 'script', content=mark_safe( js_content ) ) + +def typeahead_choices( f_value ) : + return 'var choices = [' + \ + ', '.join([ \ + '{key: ' + (str(choice[0]) if choice[0] != '' else '""') + \ + ', value: "' + str(choice[1]) + '"}' \ + for choice in f_value.choices \ + ]) + \ + '];' + +def typeahead_engine () : + return 'var choices = new Bloodhound({ ' \ + 'datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"), ' \ + 'queryTokenizer: Bloodhound.tokenizers.whitespace, ' \ + 'local: choices, ' \ + 'identify: function(obj) { return obj.value; } ' \ + '});' + +def typeahead_datasets( f_name ) : + return '{ ' \ + 'hint: true, ' \ + 'highlight: true, ' \ + 'minLength: 1 ' \ + '}, ' \ + '{ ' \ + 'templates: { ' \ + 'suggestion: Handlebars.compile("
{{value}}
") ' \ + '}, ' \ + 'display: "value", ' \ + 'name: "'+f_name+'", ' \ + 'source: choices ' \ + '}' + +def typeahead_updater( f_name ): + return 'function(evt, item) { ' \ + '$("#'+select_id(f_name)+'").val( item.key ); ' \ + 'return item; ' \ + '}' + diff --git a/machines/views.py b/machines/views.py index 951671c4..4268519b 100644 --- a/machines/views.py +++ b/machines/views.py @@ -117,11 +117,7 @@ def new_machine(request, userid): reversion.set_comment("Création") messages.success(request, "La machine a été créée") return redirect("/users/profil/" + str(user.id)) - type_to_ipv4 = {} - for t in interface.fields['type'].queryset : - iplist = IpList.objects.filter(interface__isnull=True).filter(ip_type=t.ip_type) - type_to_ipv4[str(t.id)] = ','.join(['{id:"%s", ip:"%s"}' % (b.id, b.ipv4) for b in iplist]) - return form({'machineform': machine, 'interfaceform': interface, 'domainform': domain, 'type_to_ipv4': type_to_ipv4}, 'machines/machine.html', request) + return form({'machineform': machine, 'interfaceform': interface, 'domainform': domain}, 'machines/machine.html', request) @login_required def edit_interface(request, interfaceid): @@ -158,11 +154,7 @@ def edit_interface(request, interfaceid): reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in domain_form.changed_data)) messages.success(request, "La machine a été modifiée") return redirect("/users/profil/" + str(interface.machine.user.id)) - type_to_ipv4 = {} - for t in interface_form.fields['type'].queryset : - iplist = IpList.objects.filter(interface__isnull=True).filter(ip_type=t.ip_type) - type_to_ipv4[str(t.id)] = ','.join(['{id:"%s", ip:"%s"}' % (b.id, b.ipv4) for b in iplist]) - return form({'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form, 'type_to_ipv4': type_to_ipv4}, 'machines/machine.html', request) + return form({'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form}, 'machines/machine.html', request) @login_required def del_machine(request, machineid): @@ -218,11 +210,7 @@ def new_interface(request, machineid): reversion.set_comment("Création") messages.success(request, "L'interface a été ajoutée") return redirect("/users/profil/" + str(machine.user.id)) - type_to_ipv4 = {} - for t in interface_form.fields['type'].queryset : - iplist = IpList.objects.filter(interface__isnull=True).filter(ip_type=t.ip_type) - type_to_ipv4[str(t.id)] = ','.join(['{id:"%s", ip:"%s"}' % (b.id, b.ipv4) for b in iplist]) - return form({'interfaceform': interface_form, 'domainform': domain_form, 'type_to_ipv4': type_to_ipv4}, 'machines/machine.html', request) + return form({'interfaceform': interface_form, 'domainform': domain_form}, 'machines/machine.html', request) @login_required def del_interface(request, interfaceid): diff --git a/static/css/typeaheadjs.css b/static/css/typeaheadjs.css new file mode 100644 index 00000000..64c10736 --- /dev/null +++ b/static/css/typeaheadjs.css @@ -0,0 +1,93 @@ +span.twitter-typeahead .tt-menu, +span.twitter-typeahead .tt-dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + list-style: none; + font-size: 14px; + text-align: left; + background-color: #ffffff; + border: 1px solid #cccccc; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + background-clip: padding-box; +} +span.twitter-typeahead .tt-suggestion { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.42857143; + color: #333333; + white-space: nowrap; +} +span.twitter-typeahead .tt-suggestion.tt-cursor, +span.twitter-typeahead .tt-suggestion:hover, +span.twitter-typeahead .tt-suggestion:focus { + color: #ffffff; + text-decoration: none; + outline: 0; + background-color: #337ab7; +} +.input-group.input-group-lg span.twitter-typeahead .form-control { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.input-group.input-group-sm span.twitter-typeahead .form-control { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +span.twitter-typeahead { + width: 100%; +} +.input-group span.twitter-typeahead { + display: block !important; + height: 34px; +} +.input-group span.twitter-typeahead .tt-menu, +.input-group span.twitter-typeahead .tt-dropdown-menu { + top: 32px !important; +} +.input-group span.twitter-typeahead:not(:first-child):not(:last-child) .form-control { + border-radius: 0; +} +.input-group span.twitter-typeahead:first-child .form-control { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group span.twitter-typeahead:last-child .form-control { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} +.input-group.input-group-sm span.twitter-typeahead { + height: 30px; +} +.input-group.input-group-sm span.twitter-typeahead .tt-menu, +.input-group.input-group-sm span.twitter-typeahead .tt-dropdown-menu { + top: 30px !important; +} +.input-group.input-group-lg span.twitter-typeahead { + height: 46px; +} +.input-group.input-group-lg span.twitter-typeahead .tt-menu, +.input-group.input-group-lg span.twitter-typeahead .tt-dropdown-menu { + top: 46px !important; +} diff --git a/static/js/handlebars.js b/static/js/handlebars.js new file mode 100644 index 00000000..5b566151 --- /dev/null +++ b/static/js/handlebars.js @@ -0,0 +1,4840 @@ +/**! + + @license + handlebars v4.0.10 + +Copyright (C) 2011-2016 by Yehuda Katz + +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. + +*/ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["Handlebars"] = factory(); + else + root["Handlebars"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; + +/******/ // The require function +/******/ function __webpack_require__(moduleId) { + +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; + +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; + +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); + +/******/ // Flag the module as loaded +/******/ module.loaded = true; + +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } + + +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; + +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; + +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; + +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _handlebarsRuntime = __webpack_require__(2); + + var _handlebarsRuntime2 = _interopRequireDefault(_handlebarsRuntime); + + // Compiler imports + + var _handlebarsCompilerAst = __webpack_require__(35); + + var _handlebarsCompilerAst2 = _interopRequireDefault(_handlebarsCompilerAst); + + var _handlebarsCompilerBase = __webpack_require__(36); + + var _handlebarsCompilerCompiler = __webpack_require__(41); + + var _handlebarsCompilerJavascriptCompiler = __webpack_require__(42); + + var _handlebarsCompilerJavascriptCompiler2 = _interopRequireDefault(_handlebarsCompilerJavascriptCompiler); + + var _handlebarsCompilerVisitor = __webpack_require__(39); + + var _handlebarsCompilerVisitor2 = _interopRequireDefault(_handlebarsCompilerVisitor); + + var _handlebarsNoConflict = __webpack_require__(34); + + var _handlebarsNoConflict2 = _interopRequireDefault(_handlebarsNoConflict); + + var _create = _handlebarsRuntime2['default'].create; + function create() { + var hb = _create(); + + hb.compile = function (input, options) { + return _handlebarsCompilerCompiler.compile(input, options, hb); + }; + hb.precompile = function (input, options) { + return _handlebarsCompilerCompiler.precompile(input, options, hb); + }; + + hb.AST = _handlebarsCompilerAst2['default']; + hb.Compiler = _handlebarsCompilerCompiler.Compiler; + hb.JavaScriptCompiler = _handlebarsCompilerJavascriptCompiler2['default']; + hb.Parser = _handlebarsCompilerBase.parser; + hb.parse = _handlebarsCompilerBase.parse; + + return hb; + } + + var inst = create(); + inst.create = create; + + _handlebarsNoConflict2['default'](inst); + + inst.Visitor = _handlebarsCompilerVisitor2['default']; + + inst['default'] = inst; + + exports['default'] = inst; + module.exports = exports['default']; + +/***/ }), +/* 1 */ +/***/ (function(module, exports) { + + "use strict"; + + exports["default"] = function (obj) { + return obj && obj.__esModule ? obj : { + "default": obj + }; + }; + + exports.__esModule = true; + +/***/ }), +/* 2 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireWildcard = __webpack_require__(3)['default']; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _handlebarsBase = __webpack_require__(4); + + var base = _interopRequireWildcard(_handlebarsBase); + + // Each of these augment the Handlebars object. No need to setup here. + // (This is done to easily share code between commonjs and browse envs) + + var _handlebarsSafeString = __webpack_require__(21); + + var _handlebarsSafeString2 = _interopRequireDefault(_handlebarsSafeString); + + var _handlebarsException = __webpack_require__(6); + + var _handlebarsException2 = _interopRequireDefault(_handlebarsException); + + var _handlebarsUtils = __webpack_require__(5); + + var Utils = _interopRequireWildcard(_handlebarsUtils); + + var _handlebarsRuntime = __webpack_require__(22); + + var runtime = _interopRequireWildcard(_handlebarsRuntime); + + var _handlebarsNoConflict = __webpack_require__(34); + + var _handlebarsNoConflict2 = _interopRequireDefault(_handlebarsNoConflict); + + // For compatibility and usage outside of module systems, make the Handlebars object a namespace + function create() { + var hb = new base.HandlebarsEnvironment(); + + Utils.extend(hb, base); + hb.SafeString = _handlebarsSafeString2['default']; + hb.Exception = _handlebarsException2['default']; + hb.Utils = Utils; + hb.escapeExpression = Utils.escapeExpression; + + hb.VM = runtime; + hb.template = function (spec) { + return runtime.template(spec, hb); + }; + + return hb; + } + + var inst = create(); + inst.create = create; + + _handlebarsNoConflict2['default'](inst); + + inst['default'] = inst; + + exports['default'] = inst; + module.exports = exports['default']; + +/***/ }), +/* 3 */ +/***/ (function(module, exports) { + + "use strict"; + + exports["default"] = function (obj) { + if (obj && obj.__esModule) { + return obj; + } else { + var newObj = {}; + + if (obj != null) { + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; + } + } + + newObj["default"] = obj; + return newObj; + } + }; + + exports.__esModule = true; + +/***/ }), +/* 4 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.HandlebarsEnvironment = HandlebarsEnvironment; + + var _utils = __webpack_require__(5); + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + var _helpers = __webpack_require__(10); + + var _decorators = __webpack_require__(18); + + var _logger = __webpack_require__(20); + + var _logger2 = _interopRequireDefault(_logger); + + var VERSION = '4.0.10'; + exports.VERSION = VERSION; + var COMPILER_REVISION = 7; + + exports.COMPILER_REVISION = COMPILER_REVISION; + var REVISION_CHANGES = { + 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it + 2: '== 1.0.0-rc.3', + 3: '== 1.0.0-rc.4', + 4: '== 1.x.x', + 5: '== 2.0.0-alpha.x', + 6: '>= 2.0.0-beta.1', + 7: '>= 4.0.0' + }; + + exports.REVISION_CHANGES = REVISION_CHANGES; + var objectType = '[object Object]'; + + function HandlebarsEnvironment(helpers, partials, decorators) { + this.helpers = helpers || {}; + this.partials = partials || {}; + this.decorators = decorators || {}; + + _helpers.registerDefaultHelpers(this); + _decorators.registerDefaultDecorators(this); + } + + HandlebarsEnvironment.prototype = { + constructor: HandlebarsEnvironment, + + logger: _logger2['default'], + log: _logger2['default'].log, + + registerHelper: function registerHelper(name, fn) { + if (_utils.toString.call(name) === objectType) { + if (fn) { + throw new _exception2['default']('Arg not supported with multiple helpers'); + } + _utils.extend(this.helpers, name); + } else { + this.helpers[name] = fn; + } + }, + unregisterHelper: function unregisterHelper(name) { + delete this.helpers[name]; + }, + + registerPartial: function registerPartial(name, partial) { + if (_utils.toString.call(name) === objectType) { + _utils.extend(this.partials, name); + } else { + if (typeof partial === 'undefined') { + throw new _exception2['default']('Attempting to register a partial called "' + name + '" as undefined'); + } + this.partials[name] = partial; + } + }, + unregisterPartial: function unregisterPartial(name) { + delete this.partials[name]; + }, + + registerDecorator: function registerDecorator(name, fn) { + if (_utils.toString.call(name) === objectType) { + if (fn) { + throw new _exception2['default']('Arg not supported with multiple decorators'); + } + _utils.extend(this.decorators, name); + } else { + this.decorators[name] = fn; + } + }, + unregisterDecorator: function unregisterDecorator(name) { + delete this.decorators[name]; + } + }; + + var log = _logger2['default'].log; + + exports.log = log; + exports.createFrame = _utils.createFrame; + exports.logger = _logger2['default']; + +/***/ }), +/* 5 */ +/***/ (function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + exports.extend = extend; + exports.indexOf = indexOf; + exports.escapeExpression = escapeExpression; + exports.isEmpty = isEmpty; + exports.createFrame = createFrame; + exports.blockParams = blockParams; + exports.appendContextPath = appendContextPath; + var escape = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '`': '`', + '=': '=' + }; + + var badChars = /[&<>"'`=]/g, + possible = /[&<>"'`=]/; + + function escapeChar(chr) { + return escape[chr]; + } + + function extend(obj /* , ...source */) { + for (var i = 1; i < arguments.length; i++) { + for (var key in arguments[i]) { + if (Object.prototype.hasOwnProperty.call(arguments[i], key)) { + obj[key] = arguments[i][key]; + } + } + } + + return obj; + } + + var toString = Object.prototype.toString; + + exports.toString = toString; + // Sourced from lodash + // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt + /* eslint-disable func-style */ + var isFunction = function isFunction(value) { + return typeof value === 'function'; + }; + // fallback for older versions of Chrome and Safari + /* istanbul ignore next */ + if (isFunction(/x/)) { + exports.isFunction = isFunction = function (value) { + return typeof value === 'function' && toString.call(value) === '[object Function]'; + }; + } + exports.isFunction = isFunction; + + /* eslint-enable func-style */ + + /* istanbul ignore next */ + var isArray = Array.isArray || function (value) { + return value && typeof value === 'object' ? toString.call(value) === '[object Array]' : false; + }; + + exports.isArray = isArray; + // Older IE versions do not directly support indexOf so we must implement our own, sadly. + + function indexOf(array, value) { + for (var i = 0, len = array.length; i < len; i++) { + if (array[i] === value) { + return i; + } + } + return -1; + } + + function escapeExpression(string) { + if (typeof string !== 'string') { + // don't escape SafeStrings, since they're already safe + if (string && string.toHTML) { + return string.toHTML(); + } else if (string == null) { + return ''; + } else if (!string) { + return string + ''; + } + + // Force a string conversion as this will be done by the append regardless and + // the regex test will do this transparently behind the scenes, causing issues if + // an object's to string has escaped characters in it. + string = '' + string; + } + + if (!possible.test(string)) { + return string; + } + return string.replace(badChars, escapeChar); + } + + function isEmpty(value) { + if (!value && value !== 0) { + return true; + } else if (isArray(value) && value.length === 0) { + return true; + } else { + return false; + } + } + + function createFrame(object) { + var frame = extend({}, object); + frame._parent = object; + return frame; + } + + function blockParams(params, ids) { + params.path = ids; + return params; + } + + function appendContextPath(contextPath, id) { + return (contextPath ? contextPath + '.' : '') + id; + } + +/***/ }), +/* 6 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _Object$defineProperty = __webpack_require__(7)['default']; + + exports.__esModule = true; + + var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; + + function Exception(message, node) { + var loc = node && node.loc, + line = undefined, + column = undefined; + if (loc) { + line = loc.start.line; + column = loc.start.column; + + message += ' - ' + line + ':' + column; + } + + var tmp = Error.prototype.constructor.call(this, message); + + // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. + for (var idx = 0; idx < errorProps.length; idx++) { + this[errorProps[idx]] = tmp[errorProps[idx]]; + } + + /* istanbul ignore else */ + if (Error.captureStackTrace) { + Error.captureStackTrace(this, Exception); + } + + try { + if (loc) { + this.lineNumber = line; + + // Work around issue under safari where we can't directly set the column value + /* istanbul ignore next */ + if (_Object$defineProperty) { + Object.defineProperty(this, 'column', { + value: column, + enumerable: true + }); + } else { + this.column = column; + } + } + } catch (nop) { + /* Ignore if the browser is very particular */ + } + } + + Exception.prototype = new Error(); + + exports['default'] = Exception; + module.exports = exports['default']; + +/***/ }), +/* 7 */ +/***/ (function(module, exports, __webpack_require__) { + + module.exports = { "default": __webpack_require__(8), __esModule: true }; + +/***/ }), +/* 8 */ +/***/ (function(module, exports, __webpack_require__) { + + var $ = __webpack_require__(9); + module.exports = function defineProperty(it, key, desc){ + return $.setDesc(it, key, desc); + }; + +/***/ }), +/* 9 */ +/***/ (function(module, exports) { + + var $Object = Object; + module.exports = { + create: $Object.create, + getProto: $Object.getPrototypeOf, + isEnum: {}.propertyIsEnumerable, + getDesc: $Object.getOwnPropertyDescriptor, + setDesc: $Object.defineProperty, + setDescs: $Object.defineProperties, + getKeys: $Object.keys, + getNames: $Object.getOwnPropertyNames, + getSymbols: $Object.getOwnPropertySymbols, + each: [].forEach + }; + +/***/ }), +/* 10 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.registerDefaultHelpers = registerDefaultHelpers; + + var _helpersBlockHelperMissing = __webpack_require__(11); + + var _helpersBlockHelperMissing2 = _interopRequireDefault(_helpersBlockHelperMissing); + + var _helpersEach = __webpack_require__(12); + + var _helpersEach2 = _interopRequireDefault(_helpersEach); + + var _helpersHelperMissing = __webpack_require__(13); + + var _helpersHelperMissing2 = _interopRequireDefault(_helpersHelperMissing); + + var _helpersIf = __webpack_require__(14); + + var _helpersIf2 = _interopRequireDefault(_helpersIf); + + var _helpersLog = __webpack_require__(15); + + var _helpersLog2 = _interopRequireDefault(_helpersLog); + + var _helpersLookup = __webpack_require__(16); + + var _helpersLookup2 = _interopRequireDefault(_helpersLookup); + + var _helpersWith = __webpack_require__(17); + + var _helpersWith2 = _interopRequireDefault(_helpersWith); + + function registerDefaultHelpers(instance) { + _helpersBlockHelperMissing2['default'](instance); + _helpersEach2['default'](instance); + _helpersHelperMissing2['default'](instance); + _helpersIf2['default'](instance); + _helpersLog2['default'](instance); + _helpersLookup2['default'](instance); + _helpersWith2['default'](instance); + } + +/***/ }), +/* 11 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + exports['default'] = function (instance) { + instance.registerHelper('blockHelperMissing', function (context, options) { + var inverse = options.inverse, + fn = options.fn; + + if (context === true) { + return fn(this); + } else if (context === false || context == null) { + return inverse(this); + } else if (_utils.isArray(context)) { + if (context.length > 0) { + if (options.ids) { + options.ids = [options.name]; + } + + return instance.helpers.each(context, options); + } else { + return inverse(this); + } + } else { + if (options.data && options.ids) { + var data = _utils.createFrame(options.data); + data.contextPath = _utils.appendContextPath(options.data.contextPath, options.name); + options = { data: data }; + } + + return fn(context, options); + } + }); + }; + + module.exports = exports['default']; + +/***/ }), +/* 12 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + exports['default'] = function (instance) { + instance.registerHelper('each', function (context, options) { + if (!options) { + throw new _exception2['default']('Must pass iterator to #each'); + } + + var fn = options.fn, + inverse = options.inverse, + i = 0, + ret = '', + data = undefined, + contextPath = undefined; + + if (options.data && options.ids) { + contextPath = _utils.appendContextPath(options.data.contextPath, options.ids[0]) + '.'; + } + + if (_utils.isFunction(context)) { + context = context.call(this); + } + + if (options.data) { + data = _utils.createFrame(options.data); + } + + function execIteration(field, index, last) { + if (data) { + data.key = field; + data.index = index; + data.first = index === 0; + data.last = !!last; + + if (contextPath) { + data.contextPath = contextPath + field; + } + } + + ret = ret + fn(context[field], { + data: data, + blockParams: _utils.blockParams([context[field], field], [contextPath + field, null]) + }); + } + + if (context && typeof context === 'object') { + if (_utils.isArray(context)) { + for (var j = context.length; i < j; i++) { + if (i in context) { + execIteration(i, i, i === context.length - 1); + } + } + } else { + var priorKey = undefined; + + for (var key in context) { + if (context.hasOwnProperty(key)) { + // We're running the iterations one step out of sync so we can detect + // the last iteration without have to scan the object twice and create + // an itermediate keys array. + if (priorKey !== undefined) { + execIteration(priorKey, i - 1); + } + priorKey = key; + i++; + } + } + if (priorKey !== undefined) { + execIteration(priorKey, i - 1, true); + } + } + } + + if (i === 0) { + ret = inverse(this); + } + + return ret; + }); + }; + + module.exports = exports['default']; + +/***/ }), +/* 13 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + exports['default'] = function (instance) { + instance.registerHelper('helperMissing', function () /* [args, ]options */{ + if (arguments.length === 1) { + // A missing field in a {{foo}} construct. + return undefined; + } else { + // Someone is actually trying to call something, blow up. + throw new _exception2['default']('Missing helper: "' + arguments[arguments.length - 1].name + '"'); + } + }); + }; + + module.exports = exports['default']; + +/***/ }), +/* 14 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + exports['default'] = function (instance) { + instance.registerHelper('if', function (conditional, options) { + if (_utils.isFunction(conditional)) { + conditional = conditional.call(this); + } + + // Default behavior is to render the positive path if the value is truthy and not empty. + // The `includeZero` option may be set to treat the condtional as purely not empty based on the + // behavior of isEmpty. Effectively this determines if 0 is handled by the positive path or negative. + if (!options.hash.includeZero && !conditional || _utils.isEmpty(conditional)) { + return options.inverse(this); + } else { + return options.fn(this); + } + }); + + instance.registerHelper('unless', function (conditional, options) { + return instance.helpers['if'].call(this, conditional, { fn: options.inverse, inverse: options.fn, hash: options.hash }); + }); + }; + + module.exports = exports['default']; + +/***/ }), +/* 15 */ +/***/ (function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + + exports['default'] = function (instance) { + instance.registerHelper('log', function () /* message, options */{ + var args = [undefined], + options = arguments[arguments.length - 1]; + for (var i = 0; i < arguments.length - 1; i++) { + args.push(arguments[i]); + } + + var level = 1; + if (options.hash.level != null) { + level = options.hash.level; + } else if (options.data && options.data.level != null) { + level = options.data.level; + } + args[0] = level; + + instance.log.apply(instance, args); + }); + }; + + module.exports = exports['default']; + +/***/ }), +/* 16 */ +/***/ (function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + + exports['default'] = function (instance) { + instance.registerHelper('lookup', function (obj, field) { + return obj && obj[field]; + }); + }; + + module.exports = exports['default']; + +/***/ }), +/* 17 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + exports['default'] = function (instance) { + instance.registerHelper('with', function (context, options) { + if (_utils.isFunction(context)) { + context = context.call(this); + } + + var fn = options.fn; + + if (!_utils.isEmpty(context)) { + var data = options.data; + if (options.data && options.ids) { + data = _utils.createFrame(options.data); + data.contextPath = _utils.appendContextPath(options.data.contextPath, options.ids[0]); + } + + return fn(context, { + data: data, + blockParams: _utils.blockParams([context], [data && data.contextPath]) + }); + } else { + return options.inverse(this); + } + }); + }; + + module.exports = exports['default']; + +/***/ }), +/* 18 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.registerDefaultDecorators = registerDefaultDecorators; + + var _decoratorsInline = __webpack_require__(19); + + var _decoratorsInline2 = _interopRequireDefault(_decoratorsInline); + + function registerDefaultDecorators(instance) { + _decoratorsInline2['default'](instance); + } + +/***/ }), +/* 19 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + exports['default'] = function (instance) { + instance.registerDecorator('inline', function (fn, props, container, options) { + var ret = fn; + if (!props.partials) { + props.partials = {}; + ret = function (context, options) { + // Create a new partials stack frame prior to exec. + var original = container.partials; + container.partials = _utils.extend({}, original, props.partials); + var ret = fn(context, options); + container.partials = original; + return ret; + }; + } + + props.partials[options.args[0]] = options.fn; + + return ret; + }); + }; + + module.exports = exports['default']; + +/***/ }), +/* 20 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + var logger = { + methodMap: ['debug', 'info', 'warn', 'error'], + level: 'info', + + // Maps a given level value to the `methodMap` indexes above. + lookupLevel: function lookupLevel(level) { + if (typeof level === 'string') { + var levelMap = _utils.indexOf(logger.methodMap, level.toLowerCase()); + if (levelMap >= 0) { + level = levelMap; + } else { + level = parseInt(level, 10); + } + } + + return level; + }, + + // Can be overridden in the host environment + log: function log(level) { + level = logger.lookupLevel(level); + + if (typeof console !== 'undefined' && logger.lookupLevel(logger.level) <= level) { + var method = logger.methodMap[level]; + if (!console[method]) { + // eslint-disable-line no-console + method = 'log'; + } + + for (var _len = arguments.length, message = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + message[_key - 1] = arguments[_key]; + } + + console[method].apply(console, message); // eslint-disable-line no-console + } + } + }; + + exports['default'] = logger; + module.exports = exports['default']; + +/***/ }), +/* 21 */ +/***/ (function(module, exports) { + + // Build out our basic SafeString type + 'use strict'; + + exports.__esModule = true; + function SafeString(string) { + this.string = string; + } + + SafeString.prototype.toString = SafeString.prototype.toHTML = function () { + return '' + this.string; + }; + + exports['default'] = SafeString; + module.exports = exports['default']; + +/***/ }), +/* 22 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _Object$seal = __webpack_require__(23)['default']; + + var _interopRequireWildcard = __webpack_require__(3)['default']; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.checkRevision = checkRevision; + exports.template = template; + exports.wrapProgram = wrapProgram; + exports.resolvePartial = resolvePartial; + exports.invokePartial = invokePartial; + exports.noop = noop; + + var _utils = __webpack_require__(5); + + var Utils = _interopRequireWildcard(_utils); + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + var _base = __webpack_require__(4); + + function checkRevision(compilerInfo) { + var compilerRevision = compilerInfo && compilerInfo[0] || 1, + currentRevision = _base.COMPILER_REVISION; + + if (compilerRevision !== currentRevision) { + if (compilerRevision < currentRevision) { + var runtimeVersions = _base.REVISION_CHANGES[currentRevision], + compilerVersions = _base.REVISION_CHANGES[compilerRevision]; + throw new _exception2['default']('Template was precompiled with an older version of Handlebars than the current runtime. ' + 'Please update your precompiler to a newer version (' + runtimeVersions + ') or downgrade your runtime to an older version (' + compilerVersions + ').'); + } else { + // Use the embedded version info since the runtime doesn't know about this revision yet + throw new _exception2['default']('Template was precompiled with a newer version of Handlebars than the current runtime. ' + 'Please update your runtime to a newer version (' + compilerInfo[1] + ').'); + } + } + } + + function template(templateSpec, env) { + /* istanbul ignore next */ + if (!env) { + throw new _exception2['default']('No environment passed to template'); + } + if (!templateSpec || !templateSpec.main) { + throw new _exception2['default']('Unknown template object: ' + typeof templateSpec); + } + + templateSpec.main.decorator = templateSpec.main_d; + + // Note: Using env.VM references rather than local var references throughout this section to allow + // for external users to override these as psuedo-supported APIs. + env.VM.checkRevision(templateSpec.compiler); + + function invokePartialWrapper(partial, context, options) { + if (options.hash) { + context = Utils.extend({}, context, options.hash); + if (options.ids) { + options.ids[0] = true; + } + } + + partial = env.VM.resolvePartial.call(this, partial, context, options); + var result = env.VM.invokePartial.call(this, partial, context, options); + + if (result == null && env.compile) { + options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env); + result = options.partials[options.name](context, options); + } + if (result != null) { + if (options.indent) { + var lines = result.split('\n'); + for (var i = 0, l = lines.length; i < l; i++) { + if (!lines[i] && i + 1 === l) { + break; + } + + lines[i] = options.indent + lines[i]; + } + result = lines.join('\n'); + } + return result; + } else { + throw new _exception2['default']('The partial ' + options.name + ' could not be compiled when running in runtime-only mode'); + } + } + + // Just add water + var container = { + strict: function strict(obj, name) { + if (!(name in obj)) { + throw new _exception2['default']('"' + name + '" not defined in ' + obj); + } + return obj[name]; + }, + lookup: function lookup(depths, name) { + var len = depths.length; + for (var i = 0; i < len; i++) { + if (depths[i] && depths[i][name] != null) { + return depths[i][name]; + } + } + }, + lambda: function lambda(current, context) { + return typeof current === 'function' ? current.call(context) : current; + }, + + escapeExpression: Utils.escapeExpression, + invokePartial: invokePartialWrapper, + + fn: function fn(i) { + var ret = templateSpec[i]; + ret.decorator = templateSpec[i + '_d']; + return ret; + }, + + programs: [], + program: function program(i, data, declaredBlockParams, blockParams, depths) { + var programWrapper = this.programs[i], + fn = this.fn(i); + if (data || depths || blockParams || declaredBlockParams) { + programWrapper = wrapProgram(this, i, fn, data, declaredBlockParams, blockParams, depths); + } else if (!programWrapper) { + programWrapper = this.programs[i] = wrapProgram(this, i, fn); + } + return programWrapper; + }, + + data: function data(value, depth) { + while (value && depth--) { + value = value._parent; + } + return value; + }, + merge: function merge(param, common) { + var obj = param || common; + + if (param && common && param !== common) { + obj = Utils.extend({}, common, param); + } + + return obj; + }, + // An empty object to use as replacement for null-contexts + nullContext: _Object$seal({}), + + noop: env.VM.noop, + compilerInfo: templateSpec.compiler + }; + + function ret(context) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var data = options.data; + + ret._setup(options); + if (!options.partial && templateSpec.useData) { + data = initData(context, data); + } + var depths = undefined, + blockParams = templateSpec.useBlockParams ? [] : undefined; + if (templateSpec.useDepths) { + if (options.depths) { + depths = context != options.depths[0] ? [context].concat(options.depths) : options.depths; + } else { + depths = [context]; + } + } + + function main(context /*, options*/) { + return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths); + } + main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams); + return main(context, options); + } + ret.isTop = true; + + ret._setup = function (options) { + if (!options.partial) { + container.helpers = container.merge(options.helpers, env.helpers); + + if (templateSpec.usePartial) { + container.partials = container.merge(options.partials, env.partials); + } + if (templateSpec.usePartial || templateSpec.useDecorators) { + container.decorators = container.merge(options.decorators, env.decorators); + } + } else { + container.helpers = options.helpers; + container.partials = options.partials; + container.decorators = options.decorators; + } + }; + + ret._child = function (i, data, blockParams, depths) { + if (templateSpec.useBlockParams && !blockParams) { + throw new _exception2['default']('must pass block params'); + } + if (templateSpec.useDepths && !depths) { + throw new _exception2['default']('must pass parent depths'); + } + + return wrapProgram(container, i, templateSpec[i], data, 0, blockParams, depths); + }; + return ret; + } + + function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) { + function prog(context) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + var currentDepths = depths; + if (depths && context != depths[0] && !(context === container.nullContext && depths[0] === null)) { + currentDepths = [context].concat(depths); + } + + return fn(container, context, container.helpers, container.partials, options.data || data, blockParams && [options.blockParams].concat(blockParams), currentDepths); + } + + prog = executeDecorators(fn, prog, container, depths, data, blockParams); + + prog.program = i; + prog.depth = depths ? depths.length : 0; + prog.blockParams = declaredBlockParams || 0; + return prog; + } + + function resolvePartial(partial, context, options) { + if (!partial) { + if (options.name === '@partial-block') { + partial = options.data['partial-block']; + } else { + partial = options.partials[options.name]; + } + } else if (!partial.call && !options.name) { + // This is a dynamic partial that returned a string + options.name = partial; + partial = options.partials[partial]; + } + return partial; + } + + function invokePartial(partial, context, options) { + // Use the current closure context to save the partial-block if this partial + var currentPartialBlock = options.data && options.data['partial-block']; + options.partial = true; + if (options.ids) { + options.data.contextPath = options.ids[0] || options.data.contextPath; + } + + var partialBlock = undefined; + if (options.fn && options.fn !== noop) { + (function () { + options.data = _base.createFrame(options.data); + // Wrapper function to get access to currentPartialBlock from the closure + var fn = options.fn; + partialBlock = options.data['partial-block'] = function partialBlockWrapper(context) { + var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1]; + + // Restore the partial-block from the closure for the execution of the block + // i.e. the part inside the block of the partial call. + options.data = _base.createFrame(options.data); + options.data['partial-block'] = currentPartialBlock; + return fn(context, options); + }; + if (fn.partials) { + options.partials = Utils.extend({}, options.partials, fn.partials); + } + })(); + } + + if (partial === undefined && partialBlock) { + partial = partialBlock; + } + + if (partial === undefined) { + throw new _exception2['default']('The partial ' + options.name + ' could not be found'); + } else if (partial instanceof Function) { + return partial(context, options); + } + } + + function noop() { + return ''; + } + + function initData(context, data) { + if (!data || !('root' in data)) { + data = data ? _base.createFrame(data) : {}; + data.root = context; + } + return data; + } + + function executeDecorators(fn, prog, container, depths, data, blockParams) { + if (fn.decorator) { + var props = {}; + prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths); + Utils.extend(prog, props); + } + return prog; + } + +/***/ }), +/* 23 */ +/***/ (function(module, exports, __webpack_require__) { + + module.exports = { "default": __webpack_require__(24), __esModule: true }; + +/***/ }), +/* 24 */ +/***/ (function(module, exports, __webpack_require__) { + + __webpack_require__(25); + module.exports = __webpack_require__(30).Object.seal; + +/***/ }), +/* 25 */ +/***/ (function(module, exports, __webpack_require__) { + + // 19.1.2.17 Object.seal(O) + var isObject = __webpack_require__(26); + + __webpack_require__(27)('seal', function($seal){ + return function seal(it){ + return $seal && isObject(it) ? $seal(it) : it; + }; + }); + +/***/ }), +/* 26 */ +/***/ (function(module, exports) { + + module.exports = function(it){ + return typeof it === 'object' ? it !== null : typeof it === 'function'; + }; + +/***/ }), +/* 27 */ +/***/ (function(module, exports, __webpack_require__) { + + // most Object methods by ES6 should accept primitives + var $export = __webpack_require__(28) + , core = __webpack_require__(30) + , fails = __webpack_require__(33); + module.exports = function(KEY, exec){ + var fn = (core.Object || {})[KEY] || Object[KEY] + , exp = {}; + exp[KEY] = exec(fn); + $export($export.S + $export.F * fails(function(){ fn(1); }), 'Object', exp); + }; + +/***/ }), +/* 28 */ +/***/ (function(module, exports, __webpack_require__) { + + var global = __webpack_require__(29) + , core = __webpack_require__(30) + , ctx = __webpack_require__(31) + , PROTOTYPE = 'prototype'; + + var $export = function(type, name, source){ + var IS_FORCED = type & $export.F + , IS_GLOBAL = type & $export.G + , IS_STATIC = type & $export.S + , IS_PROTO = type & $export.P + , IS_BIND = type & $export.B + , IS_WRAP = type & $export.W + , exports = IS_GLOBAL ? core : core[name] || (core[name] = {}) + , target = IS_GLOBAL ? global : IS_STATIC ? global[name] : (global[name] || {})[PROTOTYPE] + , key, own, out; + if(IS_GLOBAL)source = name; + for(key in source){ + // contains in native + own = !IS_FORCED && target && key in target; + if(own && key in exports)continue; + // export native or passed + out = own ? target[key] : source[key]; + // prevent global pollution for namespaces + exports[key] = IS_GLOBAL && typeof target[key] != 'function' ? source[key] + // bind timers to global for call from export context + : IS_BIND && own ? ctx(out, global) + // wrap global constructors for prevent change them in library + : IS_WRAP && target[key] == out ? (function(C){ + var F = function(param){ + return this instanceof C ? new C(param) : C(param); + }; + F[PROTOTYPE] = C[PROTOTYPE]; + return F; + // make static versions for prototype methods + })(out) : IS_PROTO && typeof out == 'function' ? ctx(Function.call, out) : out; + if(IS_PROTO)(exports[PROTOTYPE] || (exports[PROTOTYPE] = {}))[key] = out; + } + }; + // type bitmap + $export.F = 1; // forced + $export.G = 2; // global + $export.S = 4; // static + $export.P = 8; // proto + $export.B = 16; // bind + $export.W = 32; // wrap + module.exports = $export; + +/***/ }), +/* 29 */ +/***/ (function(module, exports) { + + // https://github.com/zloirock/core-js/issues/86#issuecomment-115759028 + var global = module.exports = typeof window != 'undefined' && window.Math == Math + ? window : typeof self != 'undefined' && self.Math == Math ? self : Function('return this')(); + if(typeof __g == 'number')__g = global; // eslint-disable-line no-undef + +/***/ }), +/* 30 */ +/***/ (function(module, exports) { + + var core = module.exports = {version: '1.2.6'}; + if(typeof __e == 'number')__e = core; // eslint-disable-line no-undef + +/***/ }), +/* 31 */ +/***/ (function(module, exports, __webpack_require__) { + + // optional / simple context binding + var aFunction = __webpack_require__(32); + module.exports = function(fn, that, length){ + aFunction(fn); + if(that === undefined)return fn; + switch(length){ + case 1: return function(a){ + return fn.call(that, a); + }; + case 2: return function(a, b){ + return fn.call(that, a, b); + }; + case 3: return function(a, b, c){ + return fn.call(that, a, b, c); + }; + } + return function(/* ...args */){ + return fn.apply(that, arguments); + }; + }; + +/***/ }), +/* 32 */ +/***/ (function(module, exports) { + + module.exports = function(it){ + if(typeof it != 'function')throw TypeError(it + ' is not a function!'); + return it; + }; + +/***/ }), +/* 33 */ +/***/ (function(module, exports) { + + module.exports = function(exec){ + try { + return !!exec(); + } catch(e){ + return true; + } + }; + +/***/ }), +/* 34 */ +/***/ (function(module, exports) { + + /* WEBPACK VAR INJECTION */(function(global) {/* global window */ + 'use strict'; + + exports.__esModule = true; + + exports['default'] = function (Handlebars) { + /* istanbul ignore next */ + var root = typeof global !== 'undefined' ? global : window, + $Handlebars = root.Handlebars; + /* istanbul ignore next */ + Handlebars.noConflict = function () { + if (root.Handlebars === Handlebars) { + root.Handlebars = $Handlebars; + } + return Handlebars; + }; + }; + + module.exports = exports['default']; + /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) + +/***/ }), +/* 35 */ +/***/ (function(module, exports) { + + 'use strict'; + + exports.__esModule = true; + var AST = { + // Public API used to evaluate derived attributes regarding AST nodes + helpers: { + // a mustache is definitely a helper if: + // * it is an eligible helper, and + // * it has at least one parameter or hash segment + helperExpression: function helperExpression(node) { + return node.type === 'SubExpression' || (node.type === 'MustacheStatement' || node.type === 'BlockStatement') && !!(node.params && node.params.length || node.hash); + }, + + scopedId: function scopedId(path) { + return (/^\.|this\b/.test(path.original) + ); + }, + + // an ID is simple if it only has one part, and that part is not + // `..` or `this`. + simpleId: function simpleId(path) { + return path.parts.length === 1 && !AST.helpers.scopedId(path) && !path.depth; + } + } + }; + + // Must be exported as an object rather than the root of the module as the jison lexer + // must modify the object to operate properly. + exports['default'] = AST; + module.exports = exports['default']; + +/***/ }), +/* 36 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + var _interopRequireWildcard = __webpack_require__(3)['default']; + + exports.__esModule = true; + exports.parse = parse; + + var _parser = __webpack_require__(37); + + var _parser2 = _interopRequireDefault(_parser); + + var _whitespaceControl = __webpack_require__(38); + + var _whitespaceControl2 = _interopRequireDefault(_whitespaceControl); + + var _helpers = __webpack_require__(40); + + var Helpers = _interopRequireWildcard(_helpers); + + var _utils = __webpack_require__(5); + + exports.parser = _parser2['default']; + + var yy = {}; + _utils.extend(yy, Helpers); + + function parse(input, options) { + // Just return if an already-compiled AST was passed in. + if (input.type === 'Program') { + return input; + } + + _parser2['default'].yy = yy; + + // Altering the shared object here, but this is ok as parser is a sync operation + yy.locInfo = function (locInfo) { + return new yy.SourceLocation(options && options.srcName, locInfo); + }; + + var strip = new _whitespaceControl2['default'](options); + return strip.accept(_parser2['default'].parse(input)); + } + +/***/ }), +/* 37 */ +/***/ (function(module, exports) { + + // File ignored in coverage tests via setting in .istanbul.yml + /* Jison generated parser */ + "use strict"; + + exports.__esModule = true; + var handlebars = (function () { + var parser = { trace: function trace() {}, + yy: {}, + symbols_: { "error": 2, "root": 3, "program": 4, "EOF": 5, "program_repetition0": 6, "statement": 7, "mustache": 8, "block": 9, "rawBlock": 10, "partial": 11, "partialBlock": 12, "content": 13, "COMMENT": 14, "CONTENT": 15, "openRawBlock": 16, "rawBlock_repetition_plus0": 17, "END_RAW_BLOCK": 18, "OPEN_RAW_BLOCK": 19, "helperName": 20, "openRawBlock_repetition0": 21, "openRawBlock_option0": 22, "CLOSE_RAW_BLOCK": 23, "openBlock": 24, "block_option0": 25, "closeBlock": 26, "openInverse": 27, "block_option1": 28, "OPEN_BLOCK": 29, "openBlock_repetition0": 30, "openBlock_option0": 31, "openBlock_option1": 32, "CLOSE": 33, "OPEN_INVERSE": 34, "openInverse_repetition0": 35, "openInverse_option0": 36, "openInverse_option1": 37, "openInverseChain": 38, "OPEN_INVERSE_CHAIN": 39, "openInverseChain_repetition0": 40, "openInverseChain_option0": 41, "openInverseChain_option1": 42, "inverseAndProgram": 43, "INVERSE": 44, "inverseChain": 45, "inverseChain_option0": 46, "OPEN_ENDBLOCK": 47, "OPEN": 48, "mustache_repetition0": 49, "mustache_option0": 50, "OPEN_UNESCAPED": 51, "mustache_repetition1": 52, "mustache_option1": 53, "CLOSE_UNESCAPED": 54, "OPEN_PARTIAL": 55, "partialName": 56, "partial_repetition0": 57, "partial_option0": 58, "openPartialBlock": 59, "OPEN_PARTIAL_BLOCK": 60, "openPartialBlock_repetition0": 61, "openPartialBlock_option0": 62, "param": 63, "sexpr": 64, "OPEN_SEXPR": 65, "sexpr_repetition0": 66, "sexpr_option0": 67, "CLOSE_SEXPR": 68, "hash": 69, "hash_repetition_plus0": 70, "hashSegment": 71, "ID": 72, "EQUALS": 73, "blockParams": 74, "OPEN_BLOCK_PARAMS": 75, "blockParams_repetition_plus0": 76, "CLOSE_BLOCK_PARAMS": 77, "path": 78, "dataName": 79, "STRING": 80, "NUMBER": 81, "BOOLEAN": 82, "UNDEFINED": 83, "NULL": 84, "DATA": 85, "pathSegments": 86, "SEP": 87, "$accept": 0, "$end": 1 }, + terminals_: { 2: "error", 5: "EOF", 14: "COMMENT", 15: "CONTENT", 18: "END_RAW_BLOCK", 19: "OPEN_RAW_BLOCK", 23: "CLOSE_RAW_BLOCK", 29: "OPEN_BLOCK", 33: "CLOSE", 34: "OPEN_INVERSE", 39: "OPEN_INVERSE_CHAIN", 44: "INVERSE", 47: "OPEN_ENDBLOCK", 48: "OPEN", 51: "OPEN_UNESCAPED", 54: "CLOSE_UNESCAPED", 55: "OPEN_PARTIAL", 60: "OPEN_PARTIAL_BLOCK", 65: "OPEN_SEXPR", 68: "CLOSE_SEXPR", 72: "ID", 73: "EQUALS", 75: "OPEN_BLOCK_PARAMS", 77: "CLOSE_BLOCK_PARAMS", 80: "STRING", 81: "NUMBER", 82: "BOOLEAN", 83: "UNDEFINED", 84: "NULL", 85: "DATA", 87: "SEP" }, + productions_: [0, [3, 2], [4, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [7, 1], [13, 1], [10, 3], [16, 5], [9, 4], [9, 4], [24, 6], [27, 6], [38, 6], [43, 2], [45, 3], [45, 1], [26, 3], [8, 5], [8, 5], [11, 5], [12, 3], [59, 5], [63, 1], [63, 1], [64, 5], [69, 1], [71, 3], [74, 3], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [20, 1], [56, 1], [56, 1], [79, 2], [78, 1], [86, 3], [86, 1], [6, 0], [6, 2], [17, 1], [17, 2], [21, 0], [21, 2], [22, 0], [22, 1], [25, 0], [25, 1], [28, 0], [28, 1], [30, 0], [30, 2], [31, 0], [31, 1], [32, 0], [32, 1], [35, 0], [35, 2], [36, 0], [36, 1], [37, 0], [37, 1], [40, 0], [40, 2], [41, 0], [41, 1], [42, 0], [42, 1], [46, 0], [46, 1], [49, 0], [49, 2], [50, 0], [50, 1], [52, 0], [52, 2], [53, 0], [53, 1], [57, 0], [57, 2], [58, 0], [58, 1], [61, 0], [61, 2], [62, 0], [62, 1], [66, 0], [66, 2], [67, 0], [67, 1], [70, 1], [70, 2], [76, 1], [76, 2]], + performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$ + /**/) { + + var $0 = $$.length - 1; + switch (yystate) { + case 1: + return $$[$0 - 1]; + break; + case 2: + this.$ = yy.prepareProgram($$[$0]); + break; + case 3: + this.$ = $$[$0]; + break; + case 4: + this.$ = $$[$0]; + break; + case 5: + this.$ = $$[$0]; + break; + case 6: + this.$ = $$[$0]; + break; + case 7: + this.$ = $$[$0]; + break; + case 8: + this.$ = $$[$0]; + break; + case 9: + this.$ = { + type: 'CommentStatement', + value: yy.stripComment($$[$0]), + strip: yy.stripFlags($$[$0], $$[$0]), + loc: yy.locInfo(this._$) + }; + + break; + case 10: + this.$ = { + type: 'ContentStatement', + original: $$[$0], + value: $$[$0], + loc: yy.locInfo(this._$) + }; + + break; + case 11: + this.$ = yy.prepareRawBlock($$[$0 - 2], $$[$0 - 1], $$[$0], this._$); + break; + case 12: + this.$ = { path: $$[$0 - 3], params: $$[$0 - 2], hash: $$[$0 - 1] }; + break; + case 13: + this.$ = yy.prepareBlock($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0], false, this._$); + break; + case 14: + this.$ = yy.prepareBlock($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0], true, this._$); + break; + case 15: + this.$ = { open: $$[$0 - 5], path: $$[$0 - 4], params: $$[$0 - 3], hash: $$[$0 - 2], blockParams: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 5], $$[$0]) }; + break; + case 16: + this.$ = { path: $$[$0 - 4], params: $$[$0 - 3], hash: $$[$0 - 2], blockParams: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 5], $$[$0]) }; + break; + case 17: + this.$ = { path: $$[$0 - 4], params: $$[$0 - 3], hash: $$[$0 - 2], blockParams: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 5], $$[$0]) }; + break; + case 18: + this.$ = { strip: yy.stripFlags($$[$0 - 1], $$[$0 - 1]), program: $$[$0] }; + break; + case 19: + var inverse = yy.prepareBlock($$[$0 - 2], $$[$0 - 1], $$[$0], $$[$0], false, this._$), + program = yy.prepareProgram([inverse], $$[$0 - 1].loc); + program.chained = true; + + this.$ = { strip: $$[$0 - 2].strip, program: program, chain: true }; + + break; + case 20: + this.$ = $$[$0]; + break; + case 21: + this.$ = { path: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 2], $$[$0]) }; + break; + case 22: + this.$ = yy.prepareMustache($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0 - 4], yy.stripFlags($$[$0 - 4], $$[$0]), this._$); + break; + case 23: + this.$ = yy.prepareMustache($$[$0 - 3], $$[$0 - 2], $$[$0 - 1], $$[$0 - 4], yy.stripFlags($$[$0 - 4], $$[$0]), this._$); + break; + case 24: + this.$ = { + type: 'PartialStatement', + name: $$[$0 - 3], + params: $$[$0 - 2], + hash: $$[$0 - 1], + indent: '', + strip: yy.stripFlags($$[$0 - 4], $$[$0]), + loc: yy.locInfo(this._$) + }; + + break; + case 25: + this.$ = yy.preparePartialBlock($$[$0 - 2], $$[$0 - 1], $$[$0], this._$); + break; + case 26: + this.$ = { path: $$[$0 - 3], params: $$[$0 - 2], hash: $$[$0 - 1], strip: yy.stripFlags($$[$0 - 4], $$[$0]) }; + break; + case 27: + this.$ = $$[$0]; + break; + case 28: + this.$ = $$[$0]; + break; + case 29: + this.$ = { + type: 'SubExpression', + path: $$[$0 - 3], + params: $$[$0 - 2], + hash: $$[$0 - 1], + loc: yy.locInfo(this._$) + }; + + break; + case 30: + this.$ = { type: 'Hash', pairs: $$[$0], loc: yy.locInfo(this._$) }; + break; + case 31: + this.$ = { type: 'HashPair', key: yy.id($$[$0 - 2]), value: $$[$0], loc: yy.locInfo(this._$) }; + break; + case 32: + this.$ = yy.id($$[$0 - 1]); + break; + case 33: + this.$ = $$[$0]; + break; + case 34: + this.$ = $$[$0]; + break; + case 35: + this.$ = { type: 'StringLiteral', value: $$[$0], original: $$[$0], loc: yy.locInfo(this._$) }; + break; + case 36: + this.$ = { type: 'NumberLiteral', value: Number($$[$0]), original: Number($$[$0]), loc: yy.locInfo(this._$) }; + break; + case 37: + this.$ = { type: 'BooleanLiteral', value: $$[$0] === 'true', original: $$[$0] === 'true', loc: yy.locInfo(this._$) }; + break; + case 38: + this.$ = { type: 'UndefinedLiteral', original: undefined, value: undefined, loc: yy.locInfo(this._$) }; + break; + case 39: + this.$ = { type: 'NullLiteral', original: null, value: null, loc: yy.locInfo(this._$) }; + break; + case 40: + this.$ = $$[$0]; + break; + case 41: + this.$ = $$[$0]; + break; + case 42: + this.$ = yy.preparePath(true, $$[$0], this._$); + break; + case 43: + this.$ = yy.preparePath(false, $$[$0], this._$); + break; + case 44: + $$[$0 - 2].push({ part: yy.id($$[$0]), original: $$[$0], separator: $$[$0 - 1] });this.$ = $$[$0 - 2]; + break; + case 45: + this.$ = [{ part: yy.id($$[$0]), original: $$[$0] }]; + break; + case 46: + this.$ = []; + break; + case 47: + $$[$0 - 1].push($$[$0]); + break; + case 48: + this.$ = [$$[$0]]; + break; + case 49: + $$[$0 - 1].push($$[$0]); + break; + case 50: + this.$ = []; + break; + case 51: + $$[$0 - 1].push($$[$0]); + break; + case 58: + this.$ = []; + break; + case 59: + $$[$0 - 1].push($$[$0]); + break; + case 64: + this.$ = []; + break; + case 65: + $$[$0 - 1].push($$[$0]); + break; + case 70: + this.$ = []; + break; + case 71: + $$[$0 - 1].push($$[$0]); + break; + case 78: + this.$ = []; + break; + case 79: + $$[$0 - 1].push($$[$0]); + break; + case 82: + this.$ = []; + break; + case 83: + $$[$0 - 1].push($$[$0]); + break; + case 86: + this.$ = []; + break; + case 87: + $$[$0 - 1].push($$[$0]); + break; + case 90: + this.$ = []; + break; + case 91: + $$[$0 - 1].push($$[$0]); + break; + case 94: + this.$ = []; + break; + case 95: + $$[$0 - 1].push($$[$0]); + break; + case 98: + this.$ = [$$[$0]]; + break; + case 99: + $$[$0 - 1].push($$[$0]); + break; + case 100: + this.$ = [$$[$0]]; + break; + case 101: + $$[$0 - 1].push($$[$0]); + break; + } + }, + table: [{ 3: 1, 4: 2, 5: [2, 46], 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 1: [3] }, { 5: [1, 4] }, { 5: [2, 2], 7: 5, 8: 6, 9: 7, 10: 8, 11: 9, 12: 10, 13: 11, 14: [1, 12], 15: [1, 20], 16: 17, 19: [1, 23], 24: 15, 27: 16, 29: [1, 21], 34: [1, 22], 39: [2, 2], 44: [2, 2], 47: [2, 2], 48: [1, 13], 51: [1, 14], 55: [1, 18], 59: 19, 60: [1, 24] }, { 1: [2, 1] }, { 5: [2, 47], 14: [2, 47], 15: [2, 47], 19: [2, 47], 29: [2, 47], 34: [2, 47], 39: [2, 47], 44: [2, 47], 47: [2, 47], 48: [2, 47], 51: [2, 47], 55: [2, 47], 60: [2, 47] }, { 5: [2, 3], 14: [2, 3], 15: [2, 3], 19: [2, 3], 29: [2, 3], 34: [2, 3], 39: [2, 3], 44: [2, 3], 47: [2, 3], 48: [2, 3], 51: [2, 3], 55: [2, 3], 60: [2, 3] }, { 5: [2, 4], 14: [2, 4], 15: [2, 4], 19: [2, 4], 29: [2, 4], 34: [2, 4], 39: [2, 4], 44: [2, 4], 47: [2, 4], 48: [2, 4], 51: [2, 4], 55: [2, 4], 60: [2, 4] }, { 5: [2, 5], 14: [2, 5], 15: [2, 5], 19: [2, 5], 29: [2, 5], 34: [2, 5], 39: [2, 5], 44: [2, 5], 47: [2, 5], 48: [2, 5], 51: [2, 5], 55: [2, 5], 60: [2, 5] }, { 5: [2, 6], 14: [2, 6], 15: [2, 6], 19: [2, 6], 29: [2, 6], 34: [2, 6], 39: [2, 6], 44: [2, 6], 47: [2, 6], 48: [2, 6], 51: [2, 6], 55: [2, 6], 60: [2, 6] }, { 5: [2, 7], 14: [2, 7], 15: [2, 7], 19: [2, 7], 29: [2, 7], 34: [2, 7], 39: [2, 7], 44: [2, 7], 47: [2, 7], 48: [2, 7], 51: [2, 7], 55: [2, 7], 60: [2, 7] }, { 5: [2, 8], 14: [2, 8], 15: [2, 8], 19: [2, 8], 29: [2, 8], 34: [2, 8], 39: [2, 8], 44: [2, 8], 47: [2, 8], 48: [2, 8], 51: [2, 8], 55: [2, 8], 60: [2, 8] }, { 5: [2, 9], 14: [2, 9], 15: [2, 9], 19: [2, 9], 29: [2, 9], 34: [2, 9], 39: [2, 9], 44: [2, 9], 47: [2, 9], 48: [2, 9], 51: [2, 9], 55: [2, 9], 60: [2, 9] }, { 20: 25, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 36, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 37, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 39: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 4: 38, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 13: 40, 15: [1, 20], 17: 39 }, { 20: 42, 56: 41, 64: 43, 65: [1, 44], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 45, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 5: [2, 10], 14: [2, 10], 15: [2, 10], 18: [2, 10], 19: [2, 10], 29: [2, 10], 34: [2, 10], 39: [2, 10], 44: [2, 10], 47: [2, 10], 48: [2, 10], 51: [2, 10], 55: [2, 10], 60: [2, 10] }, { 20: 46, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 47, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 48, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 42, 56: 49, 64: 43, 65: [1, 44], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [2, 78], 49: 50, 65: [2, 78], 72: [2, 78], 80: [2, 78], 81: [2, 78], 82: [2, 78], 83: [2, 78], 84: [2, 78], 85: [2, 78] }, { 23: [2, 33], 33: [2, 33], 54: [2, 33], 65: [2, 33], 68: [2, 33], 72: [2, 33], 75: [2, 33], 80: [2, 33], 81: [2, 33], 82: [2, 33], 83: [2, 33], 84: [2, 33], 85: [2, 33] }, { 23: [2, 34], 33: [2, 34], 54: [2, 34], 65: [2, 34], 68: [2, 34], 72: [2, 34], 75: [2, 34], 80: [2, 34], 81: [2, 34], 82: [2, 34], 83: [2, 34], 84: [2, 34], 85: [2, 34] }, { 23: [2, 35], 33: [2, 35], 54: [2, 35], 65: [2, 35], 68: [2, 35], 72: [2, 35], 75: [2, 35], 80: [2, 35], 81: [2, 35], 82: [2, 35], 83: [2, 35], 84: [2, 35], 85: [2, 35] }, { 23: [2, 36], 33: [2, 36], 54: [2, 36], 65: [2, 36], 68: [2, 36], 72: [2, 36], 75: [2, 36], 80: [2, 36], 81: [2, 36], 82: [2, 36], 83: [2, 36], 84: [2, 36], 85: [2, 36] }, { 23: [2, 37], 33: [2, 37], 54: [2, 37], 65: [2, 37], 68: [2, 37], 72: [2, 37], 75: [2, 37], 80: [2, 37], 81: [2, 37], 82: [2, 37], 83: [2, 37], 84: [2, 37], 85: [2, 37] }, { 23: [2, 38], 33: [2, 38], 54: [2, 38], 65: [2, 38], 68: [2, 38], 72: [2, 38], 75: [2, 38], 80: [2, 38], 81: [2, 38], 82: [2, 38], 83: [2, 38], 84: [2, 38], 85: [2, 38] }, { 23: [2, 39], 33: [2, 39], 54: [2, 39], 65: [2, 39], 68: [2, 39], 72: [2, 39], 75: [2, 39], 80: [2, 39], 81: [2, 39], 82: [2, 39], 83: [2, 39], 84: [2, 39], 85: [2, 39] }, { 23: [2, 43], 33: [2, 43], 54: [2, 43], 65: [2, 43], 68: [2, 43], 72: [2, 43], 75: [2, 43], 80: [2, 43], 81: [2, 43], 82: [2, 43], 83: [2, 43], 84: [2, 43], 85: [2, 43], 87: [1, 51] }, { 72: [1, 35], 86: 52 }, { 23: [2, 45], 33: [2, 45], 54: [2, 45], 65: [2, 45], 68: [2, 45], 72: [2, 45], 75: [2, 45], 80: [2, 45], 81: [2, 45], 82: [2, 45], 83: [2, 45], 84: [2, 45], 85: [2, 45], 87: [2, 45] }, { 52: 53, 54: [2, 82], 65: [2, 82], 72: [2, 82], 80: [2, 82], 81: [2, 82], 82: [2, 82], 83: [2, 82], 84: [2, 82], 85: [2, 82] }, { 25: 54, 38: 56, 39: [1, 58], 43: 57, 44: [1, 59], 45: 55, 47: [2, 54] }, { 28: 60, 43: 61, 44: [1, 59], 47: [2, 56] }, { 13: 63, 15: [1, 20], 18: [1, 62] }, { 15: [2, 48], 18: [2, 48] }, { 33: [2, 86], 57: 64, 65: [2, 86], 72: [2, 86], 80: [2, 86], 81: [2, 86], 82: [2, 86], 83: [2, 86], 84: [2, 86], 85: [2, 86] }, { 33: [2, 40], 65: [2, 40], 72: [2, 40], 80: [2, 40], 81: [2, 40], 82: [2, 40], 83: [2, 40], 84: [2, 40], 85: [2, 40] }, { 33: [2, 41], 65: [2, 41], 72: [2, 41], 80: [2, 41], 81: [2, 41], 82: [2, 41], 83: [2, 41], 84: [2, 41], 85: [2, 41] }, { 20: 65, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 26: 66, 47: [1, 67] }, { 30: 68, 33: [2, 58], 65: [2, 58], 72: [2, 58], 75: [2, 58], 80: [2, 58], 81: [2, 58], 82: [2, 58], 83: [2, 58], 84: [2, 58], 85: [2, 58] }, { 33: [2, 64], 35: 69, 65: [2, 64], 72: [2, 64], 75: [2, 64], 80: [2, 64], 81: [2, 64], 82: [2, 64], 83: [2, 64], 84: [2, 64], 85: [2, 64] }, { 21: 70, 23: [2, 50], 65: [2, 50], 72: [2, 50], 80: [2, 50], 81: [2, 50], 82: [2, 50], 83: [2, 50], 84: [2, 50], 85: [2, 50] }, { 33: [2, 90], 61: 71, 65: [2, 90], 72: [2, 90], 80: [2, 90], 81: [2, 90], 82: [2, 90], 83: [2, 90], 84: [2, 90], 85: [2, 90] }, { 20: 75, 33: [2, 80], 50: 72, 63: 73, 64: 76, 65: [1, 44], 69: 74, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 72: [1, 80] }, { 23: [2, 42], 33: [2, 42], 54: [2, 42], 65: [2, 42], 68: [2, 42], 72: [2, 42], 75: [2, 42], 80: [2, 42], 81: [2, 42], 82: [2, 42], 83: [2, 42], 84: [2, 42], 85: [2, 42], 87: [1, 51] }, { 20: 75, 53: 81, 54: [2, 84], 63: 82, 64: 76, 65: [1, 44], 69: 83, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 26: 84, 47: [1, 67] }, { 47: [2, 55] }, { 4: 85, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 39: [2, 46], 44: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 47: [2, 20] }, { 20: 86, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 4: 87, 6: 3, 14: [2, 46], 15: [2, 46], 19: [2, 46], 29: [2, 46], 34: [2, 46], 47: [2, 46], 48: [2, 46], 51: [2, 46], 55: [2, 46], 60: [2, 46] }, { 26: 88, 47: [1, 67] }, { 47: [2, 57] }, { 5: [2, 11], 14: [2, 11], 15: [2, 11], 19: [2, 11], 29: [2, 11], 34: [2, 11], 39: [2, 11], 44: [2, 11], 47: [2, 11], 48: [2, 11], 51: [2, 11], 55: [2, 11], 60: [2, 11] }, { 15: [2, 49], 18: [2, 49] }, { 20: 75, 33: [2, 88], 58: 89, 63: 90, 64: 76, 65: [1, 44], 69: 91, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 65: [2, 94], 66: 92, 68: [2, 94], 72: [2, 94], 80: [2, 94], 81: [2, 94], 82: [2, 94], 83: [2, 94], 84: [2, 94], 85: [2, 94] }, { 5: [2, 25], 14: [2, 25], 15: [2, 25], 19: [2, 25], 29: [2, 25], 34: [2, 25], 39: [2, 25], 44: [2, 25], 47: [2, 25], 48: [2, 25], 51: [2, 25], 55: [2, 25], 60: [2, 25] }, { 20: 93, 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 31: 94, 33: [2, 60], 63: 95, 64: 76, 65: [1, 44], 69: 96, 70: 77, 71: 78, 72: [1, 79], 75: [2, 60], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 33: [2, 66], 36: 97, 63: 98, 64: 76, 65: [1, 44], 69: 99, 70: 77, 71: 78, 72: [1, 79], 75: [2, 66], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 22: 100, 23: [2, 52], 63: 101, 64: 76, 65: [1, 44], 69: 102, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 20: 75, 33: [2, 92], 62: 103, 63: 104, 64: 76, 65: [1, 44], 69: 105, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [1, 106] }, { 33: [2, 79], 65: [2, 79], 72: [2, 79], 80: [2, 79], 81: [2, 79], 82: [2, 79], 83: [2, 79], 84: [2, 79], 85: [2, 79] }, { 33: [2, 81] }, { 23: [2, 27], 33: [2, 27], 54: [2, 27], 65: [2, 27], 68: [2, 27], 72: [2, 27], 75: [2, 27], 80: [2, 27], 81: [2, 27], 82: [2, 27], 83: [2, 27], 84: [2, 27], 85: [2, 27] }, { 23: [2, 28], 33: [2, 28], 54: [2, 28], 65: [2, 28], 68: [2, 28], 72: [2, 28], 75: [2, 28], 80: [2, 28], 81: [2, 28], 82: [2, 28], 83: [2, 28], 84: [2, 28], 85: [2, 28] }, { 23: [2, 30], 33: [2, 30], 54: [2, 30], 68: [2, 30], 71: 107, 72: [1, 108], 75: [2, 30] }, { 23: [2, 98], 33: [2, 98], 54: [2, 98], 68: [2, 98], 72: [2, 98], 75: [2, 98] }, { 23: [2, 45], 33: [2, 45], 54: [2, 45], 65: [2, 45], 68: [2, 45], 72: [2, 45], 73: [1, 109], 75: [2, 45], 80: [2, 45], 81: [2, 45], 82: [2, 45], 83: [2, 45], 84: [2, 45], 85: [2, 45], 87: [2, 45] }, { 23: [2, 44], 33: [2, 44], 54: [2, 44], 65: [2, 44], 68: [2, 44], 72: [2, 44], 75: [2, 44], 80: [2, 44], 81: [2, 44], 82: [2, 44], 83: [2, 44], 84: [2, 44], 85: [2, 44], 87: [2, 44] }, { 54: [1, 110] }, { 54: [2, 83], 65: [2, 83], 72: [2, 83], 80: [2, 83], 81: [2, 83], 82: [2, 83], 83: [2, 83], 84: [2, 83], 85: [2, 83] }, { 54: [2, 85] }, { 5: [2, 13], 14: [2, 13], 15: [2, 13], 19: [2, 13], 29: [2, 13], 34: [2, 13], 39: [2, 13], 44: [2, 13], 47: [2, 13], 48: [2, 13], 51: [2, 13], 55: [2, 13], 60: [2, 13] }, { 38: 56, 39: [1, 58], 43: 57, 44: [1, 59], 45: 112, 46: 111, 47: [2, 76] }, { 33: [2, 70], 40: 113, 65: [2, 70], 72: [2, 70], 75: [2, 70], 80: [2, 70], 81: [2, 70], 82: [2, 70], 83: [2, 70], 84: [2, 70], 85: [2, 70] }, { 47: [2, 18] }, { 5: [2, 14], 14: [2, 14], 15: [2, 14], 19: [2, 14], 29: [2, 14], 34: [2, 14], 39: [2, 14], 44: [2, 14], 47: [2, 14], 48: [2, 14], 51: [2, 14], 55: [2, 14], 60: [2, 14] }, { 33: [1, 114] }, { 33: [2, 87], 65: [2, 87], 72: [2, 87], 80: [2, 87], 81: [2, 87], 82: [2, 87], 83: [2, 87], 84: [2, 87], 85: [2, 87] }, { 33: [2, 89] }, { 20: 75, 63: 116, 64: 76, 65: [1, 44], 67: 115, 68: [2, 96], 69: 117, 70: 77, 71: 78, 72: [1, 79], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 33: [1, 118] }, { 32: 119, 33: [2, 62], 74: 120, 75: [1, 121] }, { 33: [2, 59], 65: [2, 59], 72: [2, 59], 75: [2, 59], 80: [2, 59], 81: [2, 59], 82: [2, 59], 83: [2, 59], 84: [2, 59], 85: [2, 59] }, { 33: [2, 61], 75: [2, 61] }, { 33: [2, 68], 37: 122, 74: 123, 75: [1, 121] }, { 33: [2, 65], 65: [2, 65], 72: [2, 65], 75: [2, 65], 80: [2, 65], 81: [2, 65], 82: [2, 65], 83: [2, 65], 84: [2, 65], 85: [2, 65] }, { 33: [2, 67], 75: [2, 67] }, { 23: [1, 124] }, { 23: [2, 51], 65: [2, 51], 72: [2, 51], 80: [2, 51], 81: [2, 51], 82: [2, 51], 83: [2, 51], 84: [2, 51], 85: [2, 51] }, { 23: [2, 53] }, { 33: [1, 125] }, { 33: [2, 91], 65: [2, 91], 72: [2, 91], 80: [2, 91], 81: [2, 91], 82: [2, 91], 83: [2, 91], 84: [2, 91], 85: [2, 91] }, { 33: [2, 93] }, { 5: [2, 22], 14: [2, 22], 15: [2, 22], 19: [2, 22], 29: [2, 22], 34: [2, 22], 39: [2, 22], 44: [2, 22], 47: [2, 22], 48: [2, 22], 51: [2, 22], 55: [2, 22], 60: [2, 22] }, { 23: [2, 99], 33: [2, 99], 54: [2, 99], 68: [2, 99], 72: [2, 99], 75: [2, 99] }, { 73: [1, 109] }, { 20: 75, 63: 126, 64: 76, 65: [1, 44], 72: [1, 35], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 5: [2, 23], 14: [2, 23], 15: [2, 23], 19: [2, 23], 29: [2, 23], 34: [2, 23], 39: [2, 23], 44: [2, 23], 47: [2, 23], 48: [2, 23], 51: [2, 23], 55: [2, 23], 60: [2, 23] }, { 47: [2, 19] }, { 47: [2, 77] }, { 20: 75, 33: [2, 72], 41: 127, 63: 128, 64: 76, 65: [1, 44], 69: 129, 70: 77, 71: 78, 72: [1, 79], 75: [2, 72], 78: 26, 79: 27, 80: [1, 28], 81: [1, 29], 82: [1, 30], 83: [1, 31], 84: [1, 32], 85: [1, 34], 86: 33 }, { 5: [2, 24], 14: [2, 24], 15: [2, 24], 19: [2, 24], 29: [2, 24], 34: [2, 24], 39: [2, 24], 44: [2, 24], 47: [2, 24], 48: [2, 24], 51: [2, 24], 55: [2, 24], 60: [2, 24] }, { 68: [1, 130] }, { 65: [2, 95], 68: [2, 95], 72: [2, 95], 80: [2, 95], 81: [2, 95], 82: [2, 95], 83: [2, 95], 84: [2, 95], 85: [2, 95] }, { 68: [2, 97] }, { 5: [2, 21], 14: [2, 21], 15: [2, 21], 19: [2, 21], 29: [2, 21], 34: [2, 21], 39: [2, 21], 44: [2, 21], 47: [2, 21], 48: [2, 21], 51: [2, 21], 55: [2, 21], 60: [2, 21] }, { 33: [1, 131] }, { 33: [2, 63] }, { 72: [1, 133], 76: 132 }, { 33: [1, 134] }, { 33: [2, 69] }, { 15: [2, 12] }, { 14: [2, 26], 15: [2, 26], 19: [2, 26], 29: [2, 26], 34: [2, 26], 47: [2, 26], 48: [2, 26], 51: [2, 26], 55: [2, 26], 60: [2, 26] }, { 23: [2, 31], 33: [2, 31], 54: [2, 31], 68: [2, 31], 72: [2, 31], 75: [2, 31] }, { 33: [2, 74], 42: 135, 74: 136, 75: [1, 121] }, { 33: [2, 71], 65: [2, 71], 72: [2, 71], 75: [2, 71], 80: [2, 71], 81: [2, 71], 82: [2, 71], 83: [2, 71], 84: [2, 71], 85: [2, 71] }, { 33: [2, 73], 75: [2, 73] }, { 23: [2, 29], 33: [2, 29], 54: [2, 29], 65: [2, 29], 68: [2, 29], 72: [2, 29], 75: [2, 29], 80: [2, 29], 81: [2, 29], 82: [2, 29], 83: [2, 29], 84: [2, 29], 85: [2, 29] }, { 14: [2, 15], 15: [2, 15], 19: [2, 15], 29: [2, 15], 34: [2, 15], 39: [2, 15], 44: [2, 15], 47: [2, 15], 48: [2, 15], 51: [2, 15], 55: [2, 15], 60: [2, 15] }, { 72: [1, 138], 77: [1, 137] }, { 72: [2, 100], 77: [2, 100] }, { 14: [2, 16], 15: [2, 16], 19: [2, 16], 29: [2, 16], 34: [2, 16], 44: [2, 16], 47: [2, 16], 48: [2, 16], 51: [2, 16], 55: [2, 16], 60: [2, 16] }, { 33: [1, 139] }, { 33: [2, 75] }, { 33: [2, 32] }, { 72: [2, 101], 77: [2, 101] }, { 14: [2, 17], 15: [2, 17], 19: [2, 17], 29: [2, 17], 34: [2, 17], 39: [2, 17], 44: [2, 17], 47: [2, 17], 48: [2, 17], 51: [2, 17], 55: [2, 17], 60: [2, 17] }], + defaultActions: { 4: [2, 1], 55: [2, 55], 57: [2, 20], 61: [2, 57], 74: [2, 81], 83: [2, 85], 87: [2, 18], 91: [2, 89], 102: [2, 53], 105: [2, 93], 111: [2, 19], 112: [2, 77], 117: [2, 97], 120: [2, 63], 123: [2, 69], 124: [2, 12], 136: [2, 75], 137: [2, 32] }, + parseError: function parseError(str, hash) { + throw new Error(str); + }, + parse: function parse(input) { + var self = this, + stack = [0], + vstack = [null], + lstack = [], + table = this.table, + yytext = "", + yylineno = 0, + yyleng = 0, + recovering = 0, + TERROR = 2, + EOF = 1; + this.lexer.setInput(input); + this.lexer.yy = this.yy; + this.yy.lexer = this.lexer; + this.yy.parser = this; + if (typeof this.lexer.yylloc == "undefined") this.lexer.yylloc = {}; + var yyloc = this.lexer.yylloc; + lstack.push(yyloc); + var ranges = this.lexer.options && this.lexer.options.ranges; + if (typeof this.yy.parseError === "function") this.parseError = this.yy.parseError; + function popStack(n) { + stack.length = stack.length - 2 * n; + vstack.length = vstack.length - n; + lstack.length = lstack.length - n; + } + function lex() { + var token; + token = self.lexer.lex() || 1; + if (typeof token !== "number") { + token = self.symbols_[token] || token; + } + return token; + } + var symbol, + preErrorSymbol, + state, + action, + a, + r, + yyval = {}, + p, + len, + newState, + expected; + while (true) { + state = stack[stack.length - 1]; + if (this.defaultActions[state]) { + action = this.defaultActions[state]; + } else { + if (symbol === null || typeof symbol == "undefined") { + symbol = lex(); + } + action = table[state] && table[state][symbol]; + } + if (typeof action === "undefined" || !action.length || !action[0]) { + var errStr = ""; + if (!recovering) { + expected = []; + for (p in table[state]) if (this.terminals_[p] && p > 2) { + expected.push("'" + this.terminals_[p] + "'"); + } + if (this.lexer.showPosition) { + errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'"; + } else { + errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1 ? "end of input" : "'" + (this.terminals_[symbol] || symbol) + "'"); + } + this.parseError(errStr, { text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected }); + } + } + if (action[0] instanceof Array && action.length > 1) { + throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); + } + switch (action[0]) { + case 1: + stack.push(symbol); + vstack.push(this.lexer.yytext); + lstack.push(this.lexer.yylloc); + stack.push(action[1]); + symbol = null; + if (!preErrorSymbol) { + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + if (recovering > 0) recovering--; + } else { + symbol = preErrorSymbol; + preErrorSymbol = null; + } + break; + case 2: + len = this.productions_[action[1]][1]; + yyval.$ = vstack[vstack.length - len]; + yyval._$ = { first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column }; + if (ranges) { + yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]]; + } + r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); + if (typeof r !== "undefined") { + return r; + } + if (len) { + stack = stack.slice(0, -1 * len * 2); + vstack = vstack.slice(0, -1 * len); + lstack = lstack.slice(0, -1 * len); + } + stack.push(this.productions_[action[1]][0]); + vstack.push(yyval.$); + lstack.push(yyval._$); + newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; + stack.push(newState); + break; + case 3: + return true; + } + } + return true; + } + }; + /* Jison generated lexer */ + var lexer = (function () { + var lexer = { EOF: 1, + parseError: function parseError(str, hash) { + if (this.yy.parser) { + this.yy.parser.parseError(str, hash); + } else { + throw new Error(str); + } + }, + setInput: function setInput(input) { + this._input = input; + this._more = this._less = this.done = false; + this.yylineno = this.yyleng = 0; + this.yytext = this.matched = this.match = ''; + this.conditionStack = ['INITIAL']; + this.yylloc = { first_line: 1, first_column: 0, last_line: 1, last_column: 0 }; + if (this.options.ranges) this.yylloc.range = [0, 0]; + this.offset = 0; + return this; + }, + input: function input() { + var ch = this._input[0]; + this.yytext += ch; + this.yyleng++; + this.offset++; + this.match += ch; + this.matched += ch; + var lines = ch.match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno++; + this.yylloc.last_line++; + } else { + this.yylloc.last_column++; + } + if (this.options.ranges) this.yylloc.range[1]++; + + this._input = this._input.slice(1); + return ch; + }, + unput: function unput(ch) { + var len = ch.length; + var lines = ch.split(/(?:\r\n?|\n)/g); + + this._input = ch + this._input; + this.yytext = this.yytext.substr(0, this.yytext.length - len - 1); + //this.yyleng -= len; + this.offset -= len; + var oldLines = this.match.split(/(?:\r\n?|\n)/g); + this.match = this.match.substr(0, this.match.length - 1); + this.matched = this.matched.substr(0, this.matched.length - 1); + + if (lines.length - 1) this.yylineno -= lines.length - 1; + var r = this.yylloc.range; + + this.yylloc = { first_line: this.yylloc.first_line, + last_line: this.yylineno + 1, + first_column: this.yylloc.first_column, + last_column: lines ? (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length : this.yylloc.first_column - len + }; + + if (this.options.ranges) { + this.yylloc.range = [r[0], r[0] + this.yyleng - len]; + } + return this; + }, + more: function more() { + this._more = true; + return this; + }, + less: function less(n) { + this.unput(this.match.slice(n)); + }, + pastInput: function pastInput() { + var past = this.matched.substr(0, this.matched.length - this.match.length); + return (past.length > 20 ? '...' : '') + past.substr(-20).replace(/\n/g, ""); + }, + upcomingInput: function upcomingInput() { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20 - next.length); + } + return (next.substr(0, 20) + (next.length > 20 ? '...' : '')).replace(/\n/g, ""); + }, + showPosition: function showPosition() { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c + "^"; + }, + next: function next() { + if (this.done) { + return this.EOF; + } + if (!this._input) this.done = true; + + var token, match, tempMatch, index, col, lines; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i = 0; i < rules.length; i++) { + tempMatch = this._input.match(this.rules[rules[i]]); + if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { + match = tempMatch; + index = i; + if (!this.options.flex) break; + } + } + if (match) { + lines = match[0].match(/(?:\r\n?|\n).*/g); + if (lines) this.yylineno += lines.length; + this.yylloc = { first_line: this.yylloc.last_line, + last_line: this.yylineno + 1, + first_column: this.yylloc.last_column, + last_column: lines ? lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length }; + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + if (this.options.ranges) { + this.yylloc.range = [this.offset, this.offset += this.yyleng]; + } + this._more = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, rules[index], this.conditionStack[this.conditionStack.length - 1]); + if (this.done && this._input) this.done = false; + if (token) return token;else return; + } + if (this._input === "") { + return this.EOF; + } else { + return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), { text: "", token: null, line: this.yylineno }); + } + }, + lex: function lex() { + var r = this.next(); + if (typeof r !== 'undefined') { + return r; + } else { + return this.lex(); + } + }, + begin: function begin(condition) { + this.conditionStack.push(condition); + }, + popState: function popState() { + return this.conditionStack.pop(); + }, + _currentRules: function _currentRules() { + return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules; + }, + topState: function topState() { + return this.conditionStack[this.conditionStack.length - 2]; + }, + pushState: function begin(condition) { + this.begin(condition); + } }; + lexer.options = {}; + lexer.performAction = function anonymous(yy, yy_, $avoiding_name_collisions, YY_START + /**/) { + + function strip(start, end) { + return yy_.yytext = yy_.yytext.substr(start, yy_.yyleng - end); + } + + var YYSTATE = YY_START; + switch ($avoiding_name_collisions) { + case 0: + if (yy_.yytext.slice(-2) === "\\\\") { + strip(0, 1); + this.begin("mu"); + } else if (yy_.yytext.slice(-1) === "\\") { + strip(0, 1); + this.begin("emu"); + } else { + this.begin("mu"); + } + if (yy_.yytext) return 15; + + break; + case 1: + return 15; + break; + case 2: + this.popState(); + return 15; + + break; + case 3: + this.begin('raw');return 15; + break; + case 4: + this.popState(); + // Should be using `this.topState()` below, but it currently + // returns the second top instead of the first top. Opened an + // issue about it at https://github.com/zaach/jison/issues/291 + if (this.conditionStack[this.conditionStack.length - 1] === 'raw') { + return 15; + } else { + yy_.yytext = yy_.yytext.substr(5, yy_.yyleng - 9); + return 'END_RAW_BLOCK'; + } + + break; + case 5: + return 15; + break; + case 6: + this.popState(); + return 14; + + break; + case 7: + return 65; + break; + case 8: + return 68; + break; + case 9: + return 19; + break; + case 10: + this.popState(); + this.begin('raw'); + return 23; + + break; + case 11: + return 55; + break; + case 12: + return 60; + break; + case 13: + return 29; + break; + case 14: + return 47; + break; + case 15: + this.popState();return 44; + break; + case 16: + this.popState();return 44; + break; + case 17: + return 34; + break; + case 18: + return 39; + break; + case 19: + return 51; + break; + case 20: + return 48; + break; + case 21: + this.unput(yy_.yytext); + this.popState(); + this.begin('com'); + + break; + case 22: + this.popState(); + return 14; + + break; + case 23: + return 48; + break; + case 24: + return 73; + break; + case 25: + return 72; + break; + case 26: + return 72; + break; + case 27: + return 87; + break; + case 28: + // ignore whitespace + break; + case 29: + this.popState();return 54; + break; + case 30: + this.popState();return 33; + break; + case 31: + yy_.yytext = strip(1, 2).replace(/\\"/g, '"');return 80; + break; + case 32: + yy_.yytext = strip(1, 2).replace(/\\'/g, "'");return 80; + break; + case 33: + return 85; + break; + case 34: + return 82; + break; + case 35: + return 82; + break; + case 36: + return 83; + break; + case 37: + return 84; + break; + case 38: + return 81; + break; + case 39: + return 75; + break; + case 40: + return 77; + break; + case 41: + return 72; + break; + case 42: + yy_.yytext = yy_.yytext.replace(/\\([\\\]])/g, '$1');return 72; + break; + case 43: + return 'INVALID'; + break; + case 44: + return 5; + break; + } + }; + lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/, /^(?:[^\x00]+)/, /^(?:[^\x00]{2,}?(?=(\{\{|\\\{\{|\\\\\{\{|$)))/, /^(?:\{\{\{\{(?=[^\/]))/, /^(?:\{\{\{\{\/[^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=[=}\s\/.])\}\}\}\})/, /^(?:[^\x00]*?(?=(\{\{\{\{)))/, /^(?:[\s\S]*?--(~)?\}\})/, /^(?:\()/, /^(?:\))/, /^(?:\{\{\{\{)/, /^(?:\}\}\}\})/, /^(?:\{\{(~)?>)/, /^(?:\{\{(~)?#>)/, /^(?:\{\{(~)?#\*?)/, /^(?:\{\{(~)?\/)/, /^(?:\{\{(~)?\^\s*(~)?\}\})/, /^(?:\{\{(~)?\s*else\s*(~)?\}\})/, /^(?:\{\{(~)?\^)/, /^(?:\{\{(~)?\s*else\b)/, /^(?:\{\{(~)?\{)/, /^(?:\{\{(~)?&)/, /^(?:\{\{(~)?!--)/, /^(?:\{\{(~)?![\s\S]*?\}\})/, /^(?:\{\{(~)?\*?)/, /^(?:=)/, /^(?:\.\.)/, /^(?:\.(?=([=~}\s\/.)|])))/, /^(?:[\/.])/, /^(?:\s+)/, /^(?:\}(~)?\}\})/, /^(?:(~)?\}\})/, /^(?:"(\\["]|[^"])*")/, /^(?:'(\\[']|[^'])*')/, /^(?:@)/, /^(?:true(?=([~}\s)])))/, /^(?:false(?=([~}\s)])))/, /^(?:undefined(?=([~}\s)])))/, /^(?:null(?=([~}\s)])))/, /^(?:-?[0-9]+(?:\.[0-9]+)?(?=([~}\s)])))/, /^(?:as\s+\|)/, /^(?:\|)/, /^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.)|]))))/, /^(?:\[(\\\]|[^\]])*\])/, /^(?:.)/, /^(?:$)/]; + lexer.conditions = { "mu": { "rules": [7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44], "inclusive": false }, "emu": { "rules": [2], "inclusive": false }, "com": { "rules": [6], "inclusive": false }, "raw": { "rules": [3, 4, 5], "inclusive": false }, "INITIAL": { "rules": [0, 1, 44], "inclusive": true } }; + return lexer; + })(); + parser.lexer = lexer; + function Parser() { + this.yy = {}; + }Parser.prototype = parser;parser.Parser = Parser; + return new Parser(); + })();exports["default"] = handlebars; + module.exports = exports["default"]; + +/***/ }), +/* 38 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _visitor = __webpack_require__(39); + + var _visitor2 = _interopRequireDefault(_visitor); + + function WhitespaceControl() { + var options = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0]; + + this.options = options; + } + WhitespaceControl.prototype = new _visitor2['default'](); + + WhitespaceControl.prototype.Program = function (program) { + var doStandalone = !this.options.ignoreStandalone; + + var isRoot = !this.isRootSeen; + this.isRootSeen = true; + + var body = program.body; + for (var i = 0, l = body.length; i < l; i++) { + var current = body[i], + strip = this.accept(current); + + if (!strip) { + continue; + } + + var _isPrevWhitespace = isPrevWhitespace(body, i, isRoot), + _isNextWhitespace = isNextWhitespace(body, i, isRoot), + openStandalone = strip.openStandalone && _isPrevWhitespace, + closeStandalone = strip.closeStandalone && _isNextWhitespace, + inlineStandalone = strip.inlineStandalone && _isPrevWhitespace && _isNextWhitespace; + + if (strip.close) { + omitRight(body, i, true); + } + if (strip.open) { + omitLeft(body, i, true); + } + + if (doStandalone && inlineStandalone) { + omitRight(body, i); + + if (omitLeft(body, i)) { + // If we are on a standalone node, save the indent info for partials + if (current.type === 'PartialStatement') { + // Pull out the whitespace from the final line + current.indent = /([ \t]+$)/.exec(body[i - 1].original)[1]; + } + } + } + if (doStandalone && openStandalone) { + omitRight((current.program || current.inverse).body); + + // Strip out the previous content node if it's whitespace only + omitLeft(body, i); + } + if (doStandalone && closeStandalone) { + // Always strip the next node + omitRight(body, i); + + omitLeft((current.inverse || current.program).body); + } + } + + return program; + }; + + WhitespaceControl.prototype.BlockStatement = WhitespaceControl.prototype.DecoratorBlock = WhitespaceControl.prototype.PartialBlockStatement = function (block) { + this.accept(block.program); + this.accept(block.inverse); + + // Find the inverse program that is involed with whitespace stripping. + var program = block.program || block.inverse, + inverse = block.program && block.inverse, + firstInverse = inverse, + lastInverse = inverse; + + if (inverse && inverse.chained) { + firstInverse = inverse.body[0].program; + + // Walk the inverse chain to find the last inverse that is actually in the chain. + while (lastInverse.chained) { + lastInverse = lastInverse.body[lastInverse.body.length - 1].program; + } + } + + var strip = { + open: block.openStrip.open, + close: block.closeStrip.close, + + // Determine the standalone candiacy. Basically flag our content as being possibly standalone + // so our parent can determine if we actually are standalone + openStandalone: isNextWhitespace(program.body), + closeStandalone: isPrevWhitespace((firstInverse || program).body) + }; + + if (block.openStrip.close) { + omitRight(program.body, null, true); + } + + if (inverse) { + var inverseStrip = block.inverseStrip; + + if (inverseStrip.open) { + omitLeft(program.body, null, true); + } + + if (inverseStrip.close) { + omitRight(firstInverse.body, null, true); + } + if (block.closeStrip.open) { + omitLeft(lastInverse.body, null, true); + } + + // Find standalone else statments + if (!this.options.ignoreStandalone && isPrevWhitespace(program.body) && isNextWhitespace(firstInverse.body)) { + omitLeft(program.body); + omitRight(firstInverse.body); + } + } else if (block.closeStrip.open) { + omitLeft(program.body, null, true); + } + + return strip; + }; + + WhitespaceControl.prototype.Decorator = WhitespaceControl.prototype.MustacheStatement = function (mustache) { + return mustache.strip; + }; + + WhitespaceControl.prototype.PartialStatement = WhitespaceControl.prototype.CommentStatement = function (node) { + /* istanbul ignore next */ + var strip = node.strip || {}; + return { + inlineStandalone: true, + open: strip.open, + close: strip.close + }; + }; + + function isPrevWhitespace(body, i, isRoot) { + if (i === undefined) { + i = body.length; + } + + // Nodes that end with newlines are considered whitespace (but are special + // cased for strip operations) + var prev = body[i - 1], + sibling = body[i - 2]; + if (!prev) { + return isRoot; + } + + if (prev.type === 'ContentStatement') { + return (sibling || !isRoot ? /\r?\n\s*?$/ : /(^|\r?\n)\s*?$/).test(prev.original); + } + } + function isNextWhitespace(body, i, isRoot) { + if (i === undefined) { + i = -1; + } + + var next = body[i + 1], + sibling = body[i + 2]; + if (!next) { + return isRoot; + } + + if (next.type === 'ContentStatement') { + return (sibling || !isRoot ? /^\s*?\r?\n/ : /^\s*?(\r?\n|$)/).test(next.original); + } + } + + // Marks the node to the right of the position as omitted. + // I.e. {{foo}}' ' will mark the ' ' node as omitted. + // + // If i is undefined, then the first child will be marked as such. + // + // If mulitple is truthy then all whitespace will be stripped out until non-whitespace + // content is met. + function omitRight(body, i, multiple) { + var current = body[i == null ? 0 : i + 1]; + if (!current || current.type !== 'ContentStatement' || !multiple && current.rightStripped) { + return; + } + + var original = current.value; + current.value = current.value.replace(multiple ? /^\s+/ : /^[ \t]*\r?\n?/, ''); + current.rightStripped = current.value !== original; + } + + // Marks the node to the left of the position as omitted. + // I.e. ' '{{foo}} will mark the ' ' node as omitted. + // + // If i is undefined then the last child will be marked as such. + // + // If mulitple is truthy then all whitespace will be stripped out until non-whitespace + // content is met. + function omitLeft(body, i, multiple) { + var current = body[i == null ? body.length - 1 : i - 1]; + if (!current || current.type !== 'ContentStatement' || !multiple && current.leftStripped) { + return; + } + + // We omit the last node if it's whitespace only and not preceeded by a non-content node. + var original = current.value; + current.value = current.value.replace(multiple ? /\s+$/ : /[ \t]+$/, ''); + current.leftStripped = current.value !== original; + return current.leftStripped; + } + + exports['default'] = WhitespaceControl; + module.exports = exports['default']; + +/***/ }), +/* 39 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + function Visitor() { + this.parents = []; + } + + Visitor.prototype = { + constructor: Visitor, + mutating: false, + + // Visits a given value. If mutating, will replace the value if necessary. + acceptKey: function acceptKey(node, name) { + var value = this.accept(node[name]); + if (this.mutating) { + // Hacky sanity check: This may have a few false positives for type for the helper + // methods but will generally do the right thing without a lot of overhead. + if (value && !Visitor.prototype[value.type]) { + throw new _exception2['default']('Unexpected node type "' + value.type + '" found when accepting ' + name + ' on ' + node.type); + } + node[name] = value; + } + }, + + // Performs an accept operation with added sanity check to ensure + // required keys are not removed. + acceptRequired: function acceptRequired(node, name) { + this.acceptKey(node, name); + + if (!node[name]) { + throw new _exception2['default'](node.type + ' requires ' + name); + } + }, + + // Traverses a given array. If mutating, empty respnses will be removed + // for child elements. + acceptArray: function acceptArray(array) { + for (var i = 0, l = array.length; i < l; i++) { + this.acceptKey(array, i); + + if (!array[i]) { + array.splice(i, 1); + i--; + l--; + } + } + }, + + accept: function accept(object) { + if (!object) { + return; + } + + /* istanbul ignore next: Sanity code */ + if (!this[object.type]) { + throw new _exception2['default']('Unknown type: ' + object.type, object); + } + + if (this.current) { + this.parents.unshift(this.current); + } + this.current = object; + + var ret = this[object.type](object); + + this.current = this.parents.shift(); + + if (!this.mutating || ret) { + return ret; + } else if (ret !== false) { + return object; + } + }, + + Program: function Program(program) { + this.acceptArray(program.body); + }, + + MustacheStatement: visitSubExpression, + Decorator: visitSubExpression, + + BlockStatement: visitBlock, + DecoratorBlock: visitBlock, + + PartialStatement: visitPartial, + PartialBlockStatement: function PartialBlockStatement(partial) { + visitPartial.call(this, partial); + + this.acceptKey(partial, 'program'); + }, + + ContentStatement: function ContentStatement() /* content */{}, + CommentStatement: function CommentStatement() /* comment */{}, + + SubExpression: visitSubExpression, + + PathExpression: function PathExpression() /* path */{}, + + StringLiteral: function StringLiteral() /* string */{}, + NumberLiteral: function NumberLiteral() /* number */{}, + BooleanLiteral: function BooleanLiteral() /* bool */{}, + UndefinedLiteral: function UndefinedLiteral() /* literal */{}, + NullLiteral: function NullLiteral() /* literal */{}, + + Hash: function Hash(hash) { + this.acceptArray(hash.pairs); + }, + HashPair: function HashPair(pair) { + this.acceptRequired(pair, 'value'); + } + }; + + function visitSubExpression(mustache) { + this.acceptRequired(mustache, 'path'); + this.acceptArray(mustache.params); + this.acceptKey(mustache, 'hash'); + } + function visitBlock(block) { + visitSubExpression.call(this, block); + + this.acceptKey(block, 'program'); + this.acceptKey(block, 'inverse'); + } + function visitPartial(partial) { + this.acceptRequired(partial, 'name'); + this.acceptArray(partial.params); + this.acceptKey(partial, 'hash'); + } + + exports['default'] = Visitor; + module.exports = exports['default']; + +/***/ }), +/* 40 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.SourceLocation = SourceLocation; + exports.id = id; + exports.stripFlags = stripFlags; + exports.stripComment = stripComment; + exports.preparePath = preparePath; + exports.prepareMustache = prepareMustache; + exports.prepareRawBlock = prepareRawBlock; + exports.prepareBlock = prepareBlock; + exports.prepareProgram = prepareProgram; + exports.preparePartialBlock = preparePartialBlock; + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + function validateClose(open, close) { + close = close.path ? close.path.original : close; + + if (open.path.original !== close) { + var errorNode = { loc: open.path.loc }; + + throw new _exception2['default'](open.path.original + " doesn't match " + close, errorNode); + } + } + + function SourceLocation(source, locInfo) { + this.source = source; + this.start = { + line: locInfo.first_line, + column: locInfo.first_column + }; + this.end = { + line: locInfo.last_line, + column: locInfo.last_column + }; + } + + function id(token) { + if (/^\[.*\]$/.test(token)) { + return token.substr(1, token.length - 2); + } else { + return token; + } + } + + function stripFlags(open, close) { + return { + open: open.charAt(2) === '~', + close: close.charAt(close.length - 3) === '~' + }; + } + + function stripComment(comment) { + return comment.replace(/^\{\{~?\!-?-?/, '').replace(/-?-?~?\}\}$/, ''); + } + + function preparePath(data, parts, loc) { + loc = this.locInfo(loc); + + var original = data ? '@' : '', + dig = [], + depth = 0, + depthString = ''; + + for (var i = 0, l = parts.length; i < l; i++) { + var part = parts[i].part, + + // If we have [] syntax then we do not treat path references as operators, + // i.e. foo.[this] resolves to approximately context.foo['this'] + isLiteral = parts[i].original !== part; + original += (parts[i].separator || '') + part; + + if (!isLiteral && (part === '..' || part === '.' || part === 'this')) { + if (dig.length > 0) { + throw new _exception2['default']('Invalid path: ' + original, { loc: loc }); + } else if (part === '..') { + depth++; + depthString += '../'; + } + } else { + dig.push(part); + } + } + + return { + type: 'PathExpression', + data: data, + depth: depth, + parts: dig, + original: original, + loc: loc + }; + } + + function prepareMustache(path, params, hash, open, strip, locInfo) { + // Must use charAt to support IE pre-10 + var escapeFlag = open.charAt(3) || open.charAt(2), + escaped = escapeFlag !== '{' && escapeFlag !== '&'; + + var decorator = /\*/.test(open); + return { + type: decorator ? 'Decorator' : 'MustacheStatement', + path: path, + params: params, + hash: hash, + escaped: escaped, + strip: strip, + loc: this.locInfo(locInfo) + }; + } + + function prepareRawBlock(openRawBlock, contents, close, locInfo) { + validateClose(openRawBlock, close); + + locInfo = this.locInfo(locInfo); + var program = { + type: 'Program', + body: contents, + strip: {}, + loc: locInfo + }; + + return { + type: 'BlockStatement', + path: openRawBlock.path, + params: openRawBlock.params, + hash: openRawBlock.hash, + program: program, + openStrip: {}, + inverseStrip: {}, + closeStrip: {}, + loc: locInfo + }; + } + + function prepareBlock(openBlock, program, inverseAndProgram, close, inverted, locInfo) { + if (close && close.path) { + validateClose(openBlock, close); + } + + var decorator = /\*/.test(openBlock.open); + + program.blockParams = openBlock.blockParams; + + var inverse = undefined, + inverseStrip = undefined; + + if (inverseAndProgram) { + if (decorator) { + throw new _exception2['default']('Unexpected inverse block on decorator', inverseAndProgram); + } + + if (inverseAndProgram.chain) { + inverseAndProgram.program.body[0].closeStrip = close.strip; + } + + inverseStrip = inverseAndProgram.strip; + inverse = inverseAndProgram.program; + } + + if (inverted) { + inverted = inverse; + inverse = program; + program = inverted; + } + + return { + type: decorator ? 'DecoratorBlock' : 'BlockStatement', + path: openBlock.path, + params: openBlock.params, + hash: openBlock.hash, + program: program, + inverse: inverse, + openStrip: openBlock.strip, + inverseStrip: inverseStrip, + closeStrip: close && close.strip, + loc: this.locInfo(locInfo) + }; + } + + function prepareProgram(statements, loc) { + if (!loc && statements.length) { + var firstLoc = statements[0].loc, + lastLoc = statements[statements.length - 1].loc; + + /* istanbul ignore else */ + if (firstLoc && lastLoc) { + loc = { + source: firstLoc.source, + start: { + line: firstLoc.start.line, + column: firstLoc.start.column + }, + end: { + line: lastLoc.end.line, + column: lastLoc.end.column + } + }; + } + } + + return { + type: 'Program', + body: statements, + strip: {}, + loc: loc + }; + } + + function preparePartialBlock(open, program, close, locInfo) { + validateClose(open, close); + + return { + type: 'PartialBlockStatement', + name: open.path, + params: open.params, + hash: open.hash, + program: program, + openStrip: open.strip, + closeStrip: close && close.strip, + loc: this.locInfo(locInfo) + }; + } + +/***/ }), +/* 41 */ +/***/ (function(module, exports, __webpack_require__) { + + /* eslint-disable new-cap */ + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + exports.Compiler = Compiler; + exports.precompile = precompile; + exports.compile = compile; + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + var _utils = __webpack_require__(5); + + var _ast = __webpack_require__(35); + + var _ast2 = _interopRequireDefault(_ast); + + var slice = [].slice; + + function Compiler() {} + + // the foundHelper register will disambiguate helper lookup from finding a + // function in a context. This is necessary for mustache compatibility, which + // requires that context functions in blocks are evaluated by blockHelperMissing, + // and then proceed as if the resulting value was provided to blockHelperMissing. + + Compiler.prototype = { + compiler: Compiler, + + equals: function equals(other) { + var len = this.opcodes.length; + if (other.opcodes.length !== len) { + return false; + } + + for (var i = 0; i < len; i++) { + var opcode = this.opcodes[i], + otherOpcode = other.opcodes[i]; + if (opcode.opcode !== otherOpcode.opcode || !argEquals(opcode.args, otherOpcode.args)) { + return false; + } + } + + // We know that length is the same between the two arrays because they are directly tied + // to the opcode behavior above. + len = this.children.length; + for (var i = 0; i < len; i++) { + if (!this.children[i].equals(other.children[i])) { + return false; + } + } + + return true; + }, + + guid: 0, + + compile: function compile(program, options) { + this.sourceNode = []; + this.opcodes = []; + this.children = []; + this.options = options; + this.stringParams = options.stringParams; + this.trackIds = options.trackIds; + + options.blockParams = options.blockParams || []; + + // These changes will propagate to the other compiler components + var knownHelpers = options.knownHelpers; + options.knownHelpers = { + 'helperMissing': true, + 'blockHelperMissing': true, + 'each': true, + 'if': true, + 'unless': true, + 'with': true, + 'log': true, + 'lookup': true + }; + if (knownHelpers) { + for (var _name in knownHelpers) { + /* istanbul ignore else */ + if (_name in knownHelpers) { + this.options.knownHelpers[_name] = knownHelpers[_name]; + } + } + } + + return this.accept(program); + }, + + compileProgram: function compileProgram(program) { + var childCompiler = new this.compiler(), + // eslint-disable-line new-cap + result = childCompiler.compile(program, this.options), + guid = this.guid++; + + this.usePartial = this.usePartial || result.usePartial; + + this.children[guid] = result; + this.useDepths = this.useDepths || result.useDepths; + + return guid; + }, + + accept: function accept(node) { + /* istanbul ignore next: Sanity code */ + if (!this[node.type]) { + throw new _exception2['default']('Unknown type: ' + node.type, node); + } + + this.sourceNode.unshift(node); + var ret = this[node.type](node); + this.sourceNode.shift(); + return ret; + }, + + Program: function Program(program) { + this.options.blockParams.unshift(program.blockParams); + + var body = program.body, + bodyLength = body.length; + for (var i = 0; i < bodyLength; i++) { + this.accept(body[i]); + } + + this.options.blockParams.shift(); + + this.isSimple = bodyLength === 1; + this.blockParams = program.blockParams ? program.blockParams.length : 0; + + return this; + }, + + BlockStatement: function BlockStatement(block) { + transformLiteralToPath(block); + + var program = block.program, + inverse = block.inverse; + + program = program && this.compileProgram(program); + inverse = inverse && this.compileProgram(inverse); + + var type = this.classifySexpr(block); + + if (type === 'helper') { + this.helperSexpr(block, program, inverse); + } else if (type === 'simple') { + this.simpleSexpr(block); + + // now that the simple mustache is resolved, we need to + // evaluate it by executing `blockHelperMissing` + this.opcode('pushProgram', program); + this.opcode('pushProgram', inverse); + this.opcode('emptyHash'); + this.opcode('blockValue', block.path.original); + } else { + this.ambiguousSexpr(block, program, inverse); + + // now that the simple mustache is resolved, we need to + // evaluate it by executing `blockHelperMissing` + this.opcode('pushProgram', program); + this.opcode('pushProgram', inverse); + this.opcode('emptyHash'); + this.opcode('ambiguousBlockValue'); + } + + this.opcode('append'); + }, + + DecoratorBlock: function DecoratorBlock(decorator) { + var program = decorator.program && this.compileProgram(decorator.program); + var params = this.setupFullMustacheParams(decorator, program, undefined), + path = decorator.path; + + this.useDecorators = true; + this.opcode('registerDecorator', params.length, path.original); + }, + + PartialStatement: function PartialStatement(partial) { + this.usePartial = true; + + var program = partial.program; + if (program) { + program = this.compileProgram(partial.program); + } + + var params = partial.params; + if (params.length > 1) { + throw new _exception2['default']('Unsupported number of partial arguments: ' + params.length, partial); + } else if (!params.length) { + if (this.options.explicitPartialContext) { + this.opcode('pushLiteral', 'undefined'); + } else { + params.push({ type: 'PathExpression', parts: [], depth: 0 }); + } + } + + var partialName = partial.name.original, + isDynamic = partial.name.type === 'SubExpression'; + if (isDynamic) { + this.accept(partial.name); + } + + this.setupFullMustacheParams(partial, program, undefined, true); + + var indent = partial.indent || ''; + if (this.options.preventIndent && indent) { + this.opcode('appendContent', indent); + indent = ''; + } + + this.opcode('invokePartial', isDynamic, partialName, indent); + this.opcode('append'); + }, + PartialBlockStatement: function PartialBlockStatement(partialBlock) { + this.PartialStatement(partialBlock); + }, + + MustacheStatement: function MustacheStatement(mustache) { + this.SubExpression(mustache); + + if (mustache.escaped && !this.options.noEscape) { + this.opcode('appendEscaped'); + } else { + this.opcode('append'); + } + }, + Decorator: function Decorator(decorator) { + this.DecoratorBlock(decorator); + }, + + ContentStatement: function ContentStatement(content) { + if (content.value) { + this.opcode('appendContent', content.value); + } + }, + + CommentStatement: function CommentStatement() {}, + + SubExpression: function SubExpression(sexpr) { + transformLiteralToPath(sexpr); + var type = this.classifySexpr(sexpr); + + if (type === 'simple') { + this.simpleSexpr(sexpr); + } else if (type === 'helper') { + this.helperSexpr(sexpr); + } else { + this.ambiguousSexpr(sexpr); + } + }, + ambiguousSexpr: function ambiguousSexpr(sexpr, program, inverse) { + var path = sexpr.path, + name = path.parts[0], + isBlock = program != null || inverse != null; + + this.opcode('getContext', path.depth); + + this.opcode('pushProgram', program); + this.opcode('pushProgram', inverse); + + path.strict = true; + this.accept(path); + + this.opcode('invokeAmbiguous', name, isBlock); + }, + + simpleSexpr: function simpleSexpr(sexpr) { + var path = sexpr.path; + path.strict = true; + this.accept(path); + this.opcode('resolvePossibleLambda'); + }, + + helperSexpr: function helperSexpr(sexpr, program, inverse) { + var params = this.setupFullMustacheParams(sexpr, program, inverse), + path = sexpr.path, + name = path.parts[0]; + + if (this.options.knownHelpers[name]) { + this.opcode('invokeKnownHelper', params.length, name); + } else if (this.options.knownHelpersOnly) { + throw new _exception2['default']('You specified knownHelpersOnly, but used the unknown helper ' + name, sexpr); + } else { + path.strict = true; + path.falsy = true; + + this.accept(path); + this.opcode('invokeHelper', params.length, path.original, _ast2['default'].helpers.simpleId(path)); + } + }, + + PathExpression: function PathExpression(path) { + this.addDepth(path.depth); + this.opcode('getContext', path.depth); + + var name = path.parts[0], + scoped = _ast2['default'].helpers.scopedId(path), + blockParamId = !path.depth && !scoped && this.blockParamIndex(name); + + if (blockParamId) { + this.opcode('lookupBlockParam', blockParamId, path.parts); + } else if (!name) { + // Context reference, i.e. `{{foo .}}` or `{{foo ..}}` + this.opcode('pushContext'); + } else if (path.data) { + this.options.data = true; + this.opcode('lookupData', path.depth, path.parts, path.strict); + } else { + this.opcode('lookupOnContext', path.parts, path.falsy, path.strict, scoped); + } + }, + + StringLiteral: function StringLiteral(string) { + this.opcode('pushString', string.value); + }, + + NumberLiteral: function NumberLiteral(number) { + this.opcode('pushLiteral', number.value); + }, + + BooleanLiteral: function BooleanLiteral(bool) { + this.opcode('pushLiteral', bool.value); + }, + + UndefinedLiteral: function UndefinedLiteral() { + this.opcode('pushLiteral', 'undefined'); + }, + + NullLiteral: function NullLiteral() { + this.opcode('pushLiteral', 'null'); + }, + + Hash: function Hash(hash) { + var pairs = hash.pairs, + i = 0, + l = pairs.length; + + this.opcode('pushHash'); + + for (; i < l; i++) { + this.pushParam(pairs[i].value); + } + while (i--) { + this.opcode('assignToHash', pairs[i].key); + } + this.opcode('popHash'); + }, + + // HELPERS + opcode: function opcode(name) { + this.opcodes.push({ opcode: name, args: slice.call(arguments, 1), loc: this.sourceNode[0].loc }); + }, + + addDepth: function addDepth(depth) { + if (!depth) { + return; + } + + this.useDepths = true; + }, + + classifySexpr: function classifySexpr(sexpr) { + var isSimple = _ast2['default'].helpers.simpleId(sexpr.path); + + var isBlockParam = isSimple && !!this.blockParamIndex(sexpr.path.parts[0]); + + // a mustache is an eligible helper if: + // * its id is simple (a single part, not `this` or `..`) + var isHelper = !isBlockParam && _ast2['default'].helpers.helperExpression(sexpr); + + // if a mustache is an eligible helper but not a definite + // helper, it is ambiguous, and will be resolved in a later + // pass or at runtime. + var isEligible = !isBlockParam && (isHelper || isSimple); + + // if ambiguous, we can possibly resolve the ambiguity now + // An eligible helper is one that does not have a complex path, i.e. `this.foo`, `../foo` etc. + if (isEligible && !isHelper) { + var _name2 = sexpr.path.parts[0], + options = this.options; + + if (options.knownHelpers[_name2]) { + isHelper = true; + } else if (options.knownHelpersOnly) { + isEligible = false; + } + } + + if (isHelper) { + return 'helper'; + } else if (isEligible) { + return 'ambiguous'; + } else { + return 'simple'; + } + }, + + pushParams: function pushParams(params) { + for (var i = 0, l = params.length; i < l; i++) { + this.pushParam(params[i]); + } + }, + + pushParam: function pushParam(val) { + var value = val.value != null ? val.value : val.original || ''; + + if (this.stringParams) { + if (value.replace) { + value = value.replace(/^(\.?\.\/)*/g, '').replace(/\//g, '.'); + } + + if (val.depth) { + this.addDepth(val.depth); + } + this.opcode('getContext', val.depth || 0); + this.opcode('pushStringParam', value, val.type); + + if (val.type === 'SubExpression') { + // SubExpressions get evaluated and passed in + // in string params mode. + this.accept(val); + } + } else { + if (this.trackIds) { + var blockParamIndex = undefined; + if (val.parts && !_ast2['default'].helpers.scopedId(val) && !val.depth) { + blockParamIndex = this.blockParamIndex(val.parts[0]); + } + if (blockParamIndex) { + var blockParamChild = val.parts.slice(1).join('.'); + this.opcode('pushId', 'BlockParam', blockParamIndex, blockParamChild); + } else { + value = val.original || value; + if (value.replace) { + value = value.replace(/^this(?:\.|$)/, '').replace(/^\.\//, '').replace(/^\.$/, ''); + } + + this.opcode('pushId', val.type, value); + } + } + this.accept(val); + } + }, + + setupFullMustacheParams: function setupFullMustacheParams(sexpr, program, inverse, omitEmpty) { + var params = sexpr.params; + this.pushParams(params); + + this.opcode('pushProgram', program); + this.opcode('pushProgram', inverse); + + if (sexpr.hash) { + this.accept(sexpr.hash); + } else { + this.opcode('emptyHash', omitEmpty); + } + + return params; + }, + + blockParamIndex: function blockParamIndex(name) { + for (var depth = 0, len = this.options.blockParams.length; depth < len; depth++) { + var blockParams = this.options.blockParams[depth], + param = blockParams && _utils.indexOf(blockParams, name); + if (blockParams && param >= 0) { + return [depth, param]; + } + } + } + }; + + function precompile(input, options, env) { + if (input == null || typeof input !== 'string' && input.type !== 'Program') { + throw new _exception2['default']('You must pass a string or Handlebars AST to Handlebars.precompile. You passed ' + input); + } + + options = options || {}; + if (!('data' in options)) { + options.data = true; + } + if (options.compat) { + options.useDepths = true; + } + + var ast = env.parse(input, options), + environment = new env.Compiler().compile(ast, options); + return new env.JavaScriptCompiler().compile(environment, options); + } + + function compile(input, options, env) { + if (options === undefined) options = {}; + + if (input == null || typeof input !== 'string' && input.type !== 'Program') { + throw new _exception2['default']('You must pass a string or Handlebars AST to Handlebars.compile. You passed ' + input); + } + + options = _utils.extend({}, options); + if (!('data' in options)) { + options.data = true; + } + if (options.compat) { + options.useDepths = true; + } + + var compiled = undefined; + + function compileInput() { + var ast = env.parse(input, options), + environment = new env.Compiler().compile(ast, options), + templateSpec = new env.JavaScriptCompiler().compile(environment, options, undefined, true); + return env.template(templateSpec); + } + + // Template is only compiled on first use and cached after that point. + function ret(context, execOptions) { + if (!compiled) { + compiled = compileInput(); + } + return compiled.call(this, context, execOptions); + } + ret._setup = function (setupOptions) { + if (!compiled) { + compiled = compileInput(); + } + return compiled._setup(setupOptions); + }; + ret._child = function (i, data, blockParams, depths) { + if (!compiled) { + compiled = compileInput(); + } + return compiled._child(i, data, blockParams, depths); + }; + return ret; + } + + function argEquals(a, b) { + if (a === b) { + return true; + } + + if (_utils.isArray(a) && _utils.isArray(b) && a.length === b.length) { + for (var i = 0; i < a.length; i++) { + if (!argEquals(a[i], b[i])) { + return false; + } + } + return true; + } + } + + function transformLiteralToPath(sexpr) { + if (!sexpr.path.parts) { + var literal = sexpr.path; + // Casting to string here to make false and 0 literal values play nicely with the rest + // of the system. + sexpr.path = { + type: 'PathExpression', + data: false, + depth: 0, + parts: [literal.original + ''], + original: literal.original + '', + loc: literal.loc + }; + } + } + +/***/ }), +/* 42 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var _interopRequireDefault = __webpack_require__(1)['default']; + + exports.__esModule = true; + + var _base = __webpack_require__(4); + + var _exception = __webpack_require__(6); + + var _exception2 = _interopRequireDefault(_exception); + + var _utils = __webpack_require__(5); + + var _codeGen = __webpack_require__(43); + + var _codeGen2 = _interopRequireDefault(_codeGen); + + function Literal(value) { + this.value = value; + } + + function JavaScriptCompiler() {} + + JavaScriptCompiler.prototype = { + // PUBLIC API: You can override these methods in a subclass to provide + // alternative compiled forms for name lookup and buffering semantics + nameLookup: function nameLookup(parent, name /* , type*/) { + if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) { + return [parent, '.', name]; + } else { + return [parent, '[', JSON.stringify(name), ']']; + } + }, + depthedLookup: function depthedLookup(name) { + return [this.aliasable('container.lookup'), '(depths, "', name, '")']; + }, + + compilerInfo: function compilerInfo() { + var revision = _base.COMPILER_REVISION, + versions = _base.REVISION_CHANGES[revision]; + return [revision, versions]; + }, + + appendToBuffer: function appendToBuffer(source, location, explicit) { + // Force a source as this simplifies the merge logic. + if (!_utils.isArray(source)) { + source = [source]; + } + source = this.source.wrap(source, location); + + if (this.environment.isSimple) { + return ['return ', source, ';']; + } else if (explicit) { + // This is a case where the buffer operation occurs as a child of another + // construct, generally braces. We have to explicitly output these buffer + // operations to ensure that the emitted code goes in the correct location. + return ['buffer += ', source, ';']; + } else { + source.appendToBuffer = true; + return source; + } + }, + + initializeBuffer: function initializeBuffer() { + return this.quotedString(''); + }, + // END PUBLIC API + + compile: function compile(environment, options, context, asObject) { + this.environment = environment; + this.options = options; + this.stringParams = this.options.stringParams; + this.trackIds = this.options.trackIds; + this.precompile = !asObject; + + this.name = this.environment.name; + this.isChild = !!context; + this.context = context || { + decorators: [], + programs: [], + environments: [] + }; + + this.preamble(); + + this.stackSlot = 0; + this.stackVars = []; + this.aliases = {}; + this.registers = { list: [] }; + this.hashes = []; + this.compileStack = []; + this.inlineStack = []; + this.blockParams = []; + + this.compileChildren(environment, options); + + this.useDepths = this.useDepths || environment.useDepths || environment.useDecorators || this.options.compat; + this.useBlockParams = this.useBlockParams || environment.useBlockParams; + + var opcodes = environment.opcodes, + opcode = undefined, + firstLoc = undefined, + i = undefined, + l = undefined; + + for (i = 0, l = opcodes.length; i < l; i++) { + opcode = opcodes[i]; + + this.source.currentLocation = opcode.loc; + firstLoc = firstLoc || opcode.loc; + this[opcode.opcode].apply(this, opcode.args); + } + + // Flush any trailing content that might be pending. + this.source.currentLocation = firstLoc; + this.pushSource(''); + + /* istanbul ignore next */ + if (this.stackSlot || this.inlineStack.length || this.compileStack.length) { + throw new _exception2['default']('Compile completed with content left on stack'); + } + + if (!this.decorators.isEmpty()) { + this.useDecorators = true; + + this.decorators.prepend('var decorators = container.decorators;\n'); + this.decorators.push('return fn;'); + + if (asObject) { + this.decorators = Function.apply(this, ['fn', 'props', 'container', 'depth0', 'data', 'blockParams', 'depths', this.decorators.merge()]); + } else { + this.decorators.prepend('function(fn, props, container, depth0, data, blockParams, depths) {\n'); + this.decorators.push('}\n'); + this.decorators = this.decorators.merge(); + } + } else { + this.decorators = undefined; + } + + var fn = this.createFunctionContext(asObject); + if (!this.isChild) { + var ret = { + compiler: this.compilerInfo(), + main: fn + }; + + if (this.decorators) { + ret.main_d = this.decorators; // eslint-disable-line camelcase + ret.useDecorators = true; + } + + var _context = this.context; + var programs = _context.programs; + var decorators = _context.decorators; + + for (i = 0, l = programs.length; i < l; i++) { + if (programs[i]) { + ret[i] = programs[i]; + if (decorators[i]) { + ret[i + '_d'] = decorators[i]; + ret.useDecorators = true; + } + } + } + + if (this.environment.usePartial) { + ret.usePartial = true; + } + if (this.options.data) { + ret.useData = true; + } + if (this.useDepths) { + ret.useDepths = true; + } + if (this.useBlockParams) { + ret.useBlockParams = true; + } + if (this.options.compat) { + ret.compat = true; + } + + if (!asObject) { + ret.compiler = JSON.stringify(ret.compiler); + + this.source.currentLocation = { start: { line: 1, column: 0 } }; + ret = this.objectLiteral(ret); + + if (options.srcName) { + ret = ret.toStringWithSourceMap({ file: options.destName }); + ret.map = ret.map && ret.map.toString(); + } else { + ret = ret.toString(); + } + } else { + ret.compilerOptions = this.options; + } + + return ret; + } else { + return fn; + } + }, + + preamble: function preamble() { + // track the last context pushed into place to allow skipping the + // getContext opcode when it would be a noop + this.lastContext = 0; + this.source = new _codeGen2['default'](this.options.srcName); + this.decorators = new _codeGen2['default'](this.options.srcName); + }, + + createFunctionContext: function createFunctionContext(asObject) { + var varDeclarations = ''; + + var locals = this.stackVars.concat(this.registers.list); + if (locals.length > 0) { + varDeclarations += ', ' + locals.join(', '); + } + + // Generate minimizer alias mappings + // + // When using true SourceNodes, this will update all references to the given alias + // as the source nodes are reused in situ. For the non-source node compilation mode, + // aliases will not be used, but this case is already being run on the client and + // we aren't concern about minimizing the template size. + var aliasCount = 0; + for (var alias in this.aliases) { + // eslint-disable-line guard-for-in + var node = this.aliases[alias]; + + if (this.aliases.hasOwnProperty(alias) && node.children && node.referenceCount > 1) { + varDeclarations += ', alias' + ++aliasCount + '=' + alias; + node.children[0] = 'alias' + aliasCount; + } + } + + var params = ['container', 'depth0', 'helpers', 'partials', 'data']; + + if (this.useBlockParams || this.useDepths) { + params.push('blockParams'); + } + if (this.useDepths) { + params.push('depths'); + } + + // Perform a second pass over the output to merge content when possible + var source = this.mergeSource(varDeclarations); + + if (asObject) { + params.push(source); + + return Function.apply(this, params); + } else { + return this.source.wrap(['function(', params.join(','), ') {\n ', source, '}']); + } + }, + mergeSource: function mergeSource(varDeclarations) { + var isSimple = this.environment.isSimple, + appendOnly = !this.forceBuffer, + appendFirst = undefined, + sourceSeen = undefined, + bufferStart = undefined, + bufferEnd = undefined; + this.source.each(function (line) { + if (line.appendToBuffer) { + if (bufferStart) { + line.prepend(' + '); + } else { + bufferStart = line; + } + bufferEnd = line; + } else { + if (bufferStart) { + if (!sourceSeen) { + appendFirst = true; + } else { + bufferStart.prepend('buffer += '); + } + bufferEnd.add(';'); + bufferStart = bufferEnd = undefined; + } + + sourceSeen = true; + if (!isSimple) { + appendOnly = false; + } + } + }); + + if (appendOnly) { + if (bufferStart) { + bufferStart.prepend('return '); + bufferEnd.add(';'); + } else if (!sourceSeen) { + this.source.push('return "";'); + } + } else { + varDeclarations += ', buffer = ' + (appendFirst ? '' : this.initializeBuffer()); + + if (bufferStart) { + bufferStart.prepend('return buffer + '); + bufferEnd.add(';'); + } else { + this.source.push('return buffer;'); + } + } + + if (varDeclarations) { + this.source.prepend('var ' + varDeclarations.substring(2) + (appendFirst ? '' : ';\n')); + } + + return this.source.merge(); + }, + + // [blockValue] + // + // On stack, before: hash, inverse, program, value + // On stack, after: return value of blockHelperMissing + // + // The purpose of this opcode is to take a block of the form + // `{{#this.foo}}...{{/this.foo}}`, resolve the value of `foo`, and + // replace it on the stack with the result of properly + // invoking blockHelperMissing. + blockValue: function blockValue(name) { + var blockHelperMissing = this.aliasable('helpers.blockHelperMissing'), + params = [this.contextName(0)]; + this.setupHelperArgs(name, 0, params); + + var blockName = this.popStack(); + params.splice(1, 0, blockName); + + this.push(this.source.functionCall(blockHelperMissing, 'call', params)); + }, + + // [ambiguousBlockValue] + // + // On stack, before: hash, inverse, program, value + // Compiler value, before: lastHelper=value of last found helper, if any + // On stack, after, if no lastHelper: same as [blockValue] + // On stack, after, if lastHelper: value + ambiguousBlockValue: function ambiguousBlockValue() { + // We're being a bit cheeky and reusing the options value from the prior exec + var blockHelperMissing = this.aliasable('helpers.blockHelperMissing'), + params = [this.contextName(0)]; + this.setupHelperArgs('', 0, params, true); + + this.flushInline(); + + var current = this.topStack(); + params.splice(1, 0, current); + + this.pushSource(['if (!', this.lastHelper, ') { ', current, ' = ', this.source.functionCall(blockHelperMissing, 'call', params), '}']); + }, + + // [appendContent] + // + // On stack, before: ... + // On stack, after: ... + // + // Appends the string value of `content` to the current buffer + appendContent: function appendContent(content) { + if (this.pendingContent) { + content = this.pendingContent + content; + } else { + this.pendingLocation = this.source.currentLocation; + } + + this.pendingContent = content; + }, + + // [append] + // + // On stack, before: value, ... + // On stack, after: ... + // + // Coerces `value` to a String and appends it to the current buffer. + // + // If `value` is truthy, or 0, it is coerced into a string and appended + // Otherwise, the empty string is appended + append: function append() { + if (this.isInline()) { + this.replaceStack(function (current) { + return [' != null ? ', current, ' : ""']; + }); + + this.pushSource(this.appendToBuffer(this.popStack())); + } else { + var local = this.popStack(); + this.pushSource(['if (', local, ' != null) { ', this.appendToBuffer(local, undefined, true), ' }']); + if (this.environment.isSimple) { + this.pushSource(['else { ', this.appendToBuffer("''", undefined, true), ' }']); + } + } + }, + + // [appendEscaped] + // + // On stack, before: value, ... + // On stack, after: ... + // + // Escape `value` and append it to the buffer + appendEscaped: function appendEscaped() { + this.pushSource(this.appendToBuffer([this.aliasable('container.escapeExpression'), '(', this.popStack(), ')'])); + }, + + // [getContext] + // + // On stack, before: ... + // On stack, after: ... + // Compiler value, after: lastContext=depth + // + // Set the value of the `lastContext` compiler value to the depth + getContext: function getContext(depth) { + this.lastContext = depth; + }, + + // [pushContext] + // + // On stack, before: ... + // On stack, after: currentContext, ... + // + // Pushes the value of the current context onto the stack. + pushContext: function pushContext() { + this.pushStackLiteral(this.contextName(this.lastContext)); + }, + + // [lookupOnContext] + // + // On stack, before: ... + // On stack, after: currentContext[name], ... + // + // Looks up the value of `name` on the current context and pushes + // it onto the stack. + lookupOnContext: function lookupOnContext(parts, falsy, strict, scoped) { + var i = 0; + + if (!scoped && this.options.compat && !this.lastContext) { + // The depthed query is expected to handle the undefined logic for the root level that + // is implemented below, so we evaluate that directly in compat mode + this.push(this.depthedLookup(parts[i++])); + } else { + this.pushContext(); + } + + this.resolvePath('context', parts, i, falsy, strict); + }, + + // [lookupBlockParam] + // + // On stack, before: ... + // On stack, after: blockParam[name], ... + // + // Looks up the value of `parts` on the given block param and pushes + // it onto the stack. + lookupBlockParam: function lookupBlockParam(blockParamId, parts) { + this.useBlockParams = true; + + this.push(['blockParams[', blockParamId[0], '][', blockParamId[1], ']']); + this.resolvePath('context', parts, 1); + }, + + // [lookupData] + // + // On stack, before: ... + // On stack, after: data, ... + // + // Push the data lookup operator + lookupData: function lookupData(depth, parts, strict) { + if (!depth) { + this.pushStackLiteral('data'); + } else { + this.pushStackLiteral('container.data(data, ' + depth + ')'); + } + + this.resolvePath('data', parts, 0, true, strict); + }, + + resolvePath: function resolvePath(type, parts, i, falsy, strict) { + // istanbul ignore next + + var _this = this; + + if (this.options.strict || this.options.assumeObjects) { + this.push(strictLookup(this.options.strict && strict, this, parts, type)); + return; + } + + var len = parts.length; + for (; i < len; i++) { + /* eslint-disable no-loop-func */ + this.replaceStack(function (current) { + var lookup = _this.nameLookup(current, parts[i], type); + // We want to ensure that zero and false are handled properly if the context (falsy flag) + // needs to have the special handling for these values. + if (!falsy) { + return [' != null ? ', lookup, ' : ', current]; + } else { + // Otherwise we can use generic falsy handling + return [' && ', lookup]; + } + }); + /* eslint-enable no-loop-func */ + } + }, + + // [resolvePossibleLambda] + // + // On stack, before: value, ... + // On stack, after: resolved value, ... + // + // If the `value` is a lambda, replace it on the stack by + // the return value of the lambda + resolvePossibleLambda: function resolvePossibleLambda() { + this.push([this.aliasable('container.lambda'), '(', this.popStack(), ', ', this.contextName(0), ')']); + }, + + // [pushStringParam] + // + // On stack, before: ... + // On stack, after: string, currentContext, ... + // + // This opcode is designed for use in string mode, which + // provides the string value of a parameter along with its + // depth rather than resolving it immediately. + pushStringParam: function pushStringParam(string, type) { + this.pushContext(); + this.pushString(type); + + // If it's a subexpression, the string result + // will be pushed after this opcode. + if (type !== 'SubExpression') { + if (typeof string === 'string') { + this.pushString(string); + } else { + this.pushStackLiteral(string); + } + } + }, + + emptyHash: function emptyHash(omitEmpty) { + if (this.trackIds) { + this.push('{}'); // hashIds + } + if (this.stringParams) { + this.push('{}'); // hashContexts + this.push('{}'); // hashTypes + } + this.pushStackLiteral(omitEmpty ? 'undefined' : '{}'); + }, + pushHash: function pushHash() { + if (this.hash) { + this.hashes.push(this.hash); + } + this.hash = { values: [], types: [], contexts: [], ids: [] }; + }, + popHash: function popHash() { + var hash = this.hash; + this.hash = this.hashes.pop(); + + if (this.trackIds) { + this.push(this.objectLiteral(hash.ids)); + } + if (this.stringParams) { + this.push(this.objectLiteral(hash.contexts)); + this.push(this.objectLiteral(hash.types)); + } + + this.push(this.objectLiteral(hash.values)); + }, + + // [pushString] + // + // On stack, before: ... + // On stack, after: quotedString(string), ... + // + // Push a quoted version of `string` onto the stack + pushString: function pushString(string) { + this.pushStackLiteral(this.quotedString(string)); + }, + + // [pushLiteral] + // + // On stack, before: ... + // On stack, after: value, ... + // + // Pushes a value onto the stack. This operation prevents + // the compiler from creating a temporary variable to hold + // it. + pushLiteral: function pushLiteral(value) { + this.pushStackLiteral(value); + }, + + // [pushProgram] + // + // On stack, before: ... + // On stack, after: program(guid), ... + // + // Push a program expression onto the stack. This takes + // a compile-time guid and converts it into a runtime-accessible + // expression. + pushProgram: function pushProgram(guid) { + if (guid != null) { + this.pushStackLiteral(this.programExpression(guid)); + } else { + this.pushStackLiteral(null); + } + }, + + // [registerDecorator] + // + // On stack, before: hash, program, params..., ... + // On stack, after: ... + // + // Pops off the decorator's parameters, invokes the decorator, + // and inserts the decorator into the decorators list. + registerDecorator: function registerDecorator(paramSize, name) { + var foundDecorator = this.nameLookup('decorators', name, 'decorator'), + options = this.setupHelperArgs(name, paramSize); + + this.decorators.push(['fn = ', this.decorators.functionCall(foundDecorator, '', ['fn', 'props', 'container', options]), ' || fn;']); + }, + + // [invokeHelper] + // + // On stack, before: hash, inverse, program, params..., ... + // On stack, after: result of helper invocation + // + // Pops off the helper's parameters, invokes the helper, + // and pushes the helper's return value onto the stack. + // + // If the helper is not found, `helperMissing` is called. + invokeHelper: function invokeHelper(paramSize, name, isSimple) { + var nonHelper = this.popStack(), + helper = this.setupHelper(paramSize, name), + simple = isSimple ? [helper.name, ' || '] : ''; + + var lookup = ['('].concat(simple, nonHelper); + if (!this.options.strict) { + lookup.push(' || ', this.aliasable('helpers.helperMissing')); + } + lookup.push(')'); + + this.push(this.source.functionCall(lookup, 'call', helper.callParams)); + }, + + // [invokeKnownHelper] + // + // On stack, before: hash, inverse, program, params..., ... + // On stack, after: result of helper invocation + // + // This operation is used when the helper is known to exist, + // so a `helperMissing` fallback is not required. + invokeKnownHelper: function invokeKnownHelper(paramSize, name) { + var helper = this.setupHelper(paramSize, name); + this.push(this.source.functionCall(helper.name, 'call', helper.callParams)); + }, + + // [invokeAmbiguous] + // + // On stack, before: hash, inverse, program, params..., ... + // On stack, after: result of disambiguation + // + // This operation is used when an expression like `{{foo}}` + // is provided, but we don't know at compile-time whether it + // is a helper or a path. + // + // This operation emits more code than the other options, + // and can be avoided by passing the `knownHelpers` and + // `knownHelpersOnly` flags at compile-time. + invokeAmbiguous: function invokeAmbiguous(name, helperCall) { + this.useRegister('helper'); + + var nonHelper = this.popStack(); + + this.emptyHash(); + var helper = this.setupHelper(0, name, helperCall); + + var helperName = this.lastHelper = this.nameLookup('helpers', name, 'helper'); + + var lookup = ['(', '(helper = ', helperName, ' || ', nonHelper, ')']; + if (!this.options.strict) { + lookup[0] = '(helper = '; + lookup.push(' != null ? helper : ', this.aliasable('helpers.helperMissing')); + } + + this.push(['(', lookup, helper.paramsInit ? ['),(', helper.paramsInit] : [], '),', '(typeof helper === ', this.aliasable('"function"'), ' ? ', this.source.functionCall('helper', 'call', helper.callParams), ' : helper))']); + }, + + // [invokePartial] + // + // On stack, before: context, ... + // On stack after: result of partial invocation + // + // This operation pops off a context, invokes a partial with that context, + // and pushes the result of the invocation back. + invokePartial: function invokePartial(isDynamic, name, indent) { + var params = [], + options = this.setupParams(name, 1, params); + + if (isDynamic) { + name = this.popStack(); + delete options.name; + } + + if (indent) { + options.indent = JSON.stringify(indent); + } + options.helpers = 'helpers'; + options.partials = 'partials'; + options.decorators = 'container.decorators'; + + if (!isDynamic) { + params.unshift(this.nameLookup('partials', name, 'partial')); + } else { + params.unshift(name); + } + + if (this.options.compat) { + options.depths = 'depths'; + } + options = this.objectLiteral(options); + params.push(options); + + this.push(this.source.functionCall('container.invokePartial', '', params)); + }, + + // [assignToHash] + // + // On stack, before: value, ..., hash, ... + // On stack, after: ..., hash, ... + // + // Pops a value off the stack and assigns it to the current hash + assignToHash: function assignToHash(key) { + var value = this.popStack(), + context = undefined, + type = undefined, + id = undefined; + + if (this.trackIds) { + id = this.popStack(); + } + if (this.stringParams) { + type = this.popStack(); + context = this.popStack(); + } + + var hash = this.hash; + if (context) { + hash.contexts[key] = context; + } + if (type) { + hash.types[key] = type; + } + if (id) { + hash.ids[key] = id; + } + hash.values[key] = value; + }, + + pushId: function pushId(type, name, child) { + if (type === 'BlockParam') { + this.pushStackLiteral('blockParams[' + name[0] + '].path[' + name[1] + ']' + (child ? ' + ' + JSON.stringify('.' + child) : '')); + } else if (type === 'PathExpression') { + this.pushString(name); + } else if (type === 'SubExpression') { + this.pushStackLiteral('true'); + } else { + this.pushStackLiteral('null'); + } + }, + + // HELPERS + + compiler: JavaScriptCompiler, + + compileChildren: function compileChildren(environment, options) { + var children = environment.children, + child = undefined, + compiler = undefined; + + for (var i = 0, l = children.length; i < l; i++) { + child = children[i]; + compiler = new this.compiler(); // eslint-disable-line new-cap + + var existing = this.matchExistingProgram(child); + + if (existing == null) { + this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children + var index = this.context.programs.length; + child.index = index; + child.name = 'program' + index; + this.context.programs[index] = compiler.compile(child, options, this.context, !this.precompile); + this.context.decorators[index] = compiler.decorators; + this.context.environments[index] = child; + + this.useDepths = this.useDepths || compiler.useDepths; + this.useBlockParams = this.useBlockParams || compiler.useBlockParams; + child.useDepths = this.useDepths; + child.useBlockParams = this.useBlockParams; + } else { + child.index = existing.index; + child.name = 'program' + existing.index; + + this.useDepths = this.useDepths || existing.useDepths; + this.useBlockParams = this.useBlockParams || existing.useBlockParams; + } + } + }, + matchExistingProgram: function matchExistingProgram(child) { + for (var i = 0, len = this.context.environments.length; i < len; i++) { + var environment = this.context.environments[i]; + if (environment && environment.equals(child)) { + return environment; + } + } + }, + + programExpression: function programExpression(guid) { + var child = this.environment.children[guid], + programParams = [child.index, 'data', child.blockParams]; + + if (this.useBlockParams || this.useDepths) { + programParams.push('blockParams'); + } + if (this.useDepths) { + programParams.push('depths'); + } + + return 'container.program(' + programParams.join(', ') + ')'; + }, + + useRegister: function useRegister(name) { + if (!this.registers[name]) { + this.registers[name] = true; + this.registers.list.push(name); + } + }, + + push: function push(expr) { + if (!(expr instanceof Literal)) { + expr = this.source.wrap(expr); + } + + this.inlineStack.push(expr); + return expr; + }, + + pushStackLiteral: function pushStackLiteral(item) { + this.push(new Literal(item)); + }, + + pushSource: function pushSource(source) { + if (this.pendingContent) { + this.source.push(this.appendToBuffer(this.source.quotedString(this.pendingContent), this.pendingLocation)); + this.pendingContent = undefined; + } + + if (source) { + this.source.push(source); + } + }, + + replaceStack: function replaceStack(callback) { + var prefix = ['('], + stack = undefined, + createdStack = undefined, + usedLiteral = undefined; + + /* istanbul ignore next */ + if (!this.isInline()) { + throw new _exception2['default']('replaceStack on non-inline'); + } + + // We want to merge the inline statement into the replacement statement via ',' + var top = this.popStack(true); + + if (top instanceof Literal) { + // Literals do not need to be inlined + stack = [top.value]; + prefix = ['(', stack]; + usedLiteral = true; + } else { + // Get or create the current stack name for use by the inline + createdStack = true; + var _name = this.incrStack(); + + prefix = ['((', this.push(_name), ' = ', top, ')']; + stack = this.topStack(); + } + + var item = callback.call(this, stack); + + if (!usedLiteral) { + this.popStack(); + } + if (createdStack) { + this.stackSlot--; + } + this.push(prefix.concat(item, ')')); + }, + + incrStack: function incrStack() { + this.stackSlot++; + if (this.stackSlot > this.stackVars.length) { + this.stackVars.push('stack' + this.stackSlot); + } + return this.topStackName(); + }, + topStackName: function topStackName() { + return 'stack' + this.stackSlot; + }, + flushInline: function flushInline() { + var inlineStack = this.inlineStack; + this.inlineStack = []; + for (var i = 0, len = inlineStack.length; i < len; i++) { + var entry = inlineStack[i]; + /* istanbul ignore if */ + if (entry instanceof Literal) { + this.compileStack.push(entry); + } else { + var stack = this.incrStack(); + this.pushSource([stack, ' = ', entry, ';']); + this.compileStack.push(stack); + } + } + }, + isInline: function isInline() { + return this.inlineStack.length; + }, + + popStack: function popStack(wrapped) { + var inline = this.isInline(), + item = (inline ? this.inlineStack : this.compileStack).pop(); + + if (!wrapped && item instanceof Literal) { + return item.value; + } else { + if (!inline) { + /* istanbul ignore next */ + if (!this.stackSlot) { + throw new _exception2['default']('Invalid stack pop'); + } + this.stackSlot--; + } + return item; + } + }, + + topStack: function topStack() { + var stack = this.isInline() ? this.inlineStack : this.compileStack, + item = stack[stack.length - 1]; + + /* istanbul ignore if */ + if (item instanceof Literal) { + return item.value; + } else { + return item; + } + }, + + contextName: function contextName(context) { + if (this.useDepths && context) { + return 'depths[' + context + ']'; + } else { + return 'depth' + context; + } + }, + + quotedString: function quotedString(str) { + return this.source.quotedString(str); + }, + + objectLiteral: function objectLiteral(obj) { + return this.source.objectLiteral(obj); + }, + + aliasable: function aliasable(name) { + var ret = this.aliases[name]; + if (ret) { + ret.referenceCount++; + return ret; + } + + ret = this.aliases[name] = this.source.wrap(name); + ret.aliasable = true; + ret.referenceCount = 1; + + return ret; + }, + + setupHelper: function setupHelper(paramSize, name, blockHelper) { + var params = [], + paramsInit = this.setupHelperArgs(name, paramSize, params, blockHelper); + var foundHelper = this.nameLookup('helpers', name, 'helper'), + callContext = this.aliasable(this.contextName(0) + ' != null ? ' + this.contextName(0) + ' : (container.nullContext || {})'); + + return { + params: params, + paramsInit: paramsInit, + name: foundHelper, + callParams: [callContext].concat(params) + }; + }, + + setupParams: function setupParams(helper, paramSize, params) { + var options = {}, + contexts = [], + types = [], + ids = [], + objectArgs = !params, + param = undefined; + + if (objectArgs) { + params = []; + } + + options.name = this.quotedString(helper); + options.hash = this.popStack(); + + if (this.trackIds) { + options.hashIds = this.popStack(); + } + if (this.stringParams) { + options.hashTypes = this.popStack(); + options.hashContexts = this.popStack(); + } + + var inverse = this.popStack(), + program = this.popStack(); + + // Avoid setting fn and inverse if neither are set. This allows + // helpers to do a check for `if (options.fn)` + if (program || inverse) { + options.fn = program || 'container.noop'; + options.inverse = inverse || 'container.noop'; + } + + // The parameters go on to the stack in order (making sure that they are evaluated in order) + // so we need to pop them off the stack in reverse order + var i = paramSize; + while (i--) { + param = this.popStack(); + params[i] = param; + + if (this.trackIds) { + ids[i] = this.popStack(); + } + if (this.stringParams) { + types[i] = this.popStack(); + contexts[i] = this.popStack(); + } + } + + if (objectArgs) { + options.args = this.source.generateArray(params); + } + + if (this.trackIds) { + options.ids = this.source.generateArray(ids); + } + if (this.stringParams) { + options.types = this.source.generateArray(types); + options.contexts = this.source.generateArray(contexts); + } + + if (this.options.data) { + options.data = 'data'; + } + if (this.useBlockParams) { + options.blockParams = 'blockParams'; + } + return options; + }, + + setupHelperArgs: function setupHelperArgs(helper, paramSize, params, useRegister) { + var options = this.setupParams(helper, paramSize, params); + options = this.objectLiteral(options); + if (useRegister) { + this.useRegister('options'); + params.push('options'); + return ['options=', options]; + } else if (params) { + params.push(options); + return ''; + } else { + return options; + } + } + }; + + (function () { + var reservedWords = ('break else new var' + ' case finally return void' + ' catch for switch while' + ' continue function this with' + ' default if throw' + ' delete in try' + ' do instanceof typeof' + ' abstract enum int short' + ' boolean export interface static' + ' byte extends long super' + ' char final native synchronized' + ' class float package throws' + ' const goto private transient' + ' debugger implements protected volatile' + ' double import public let yield await' + ' null true false').split(' '); + + var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {}; + + for (var i = 0, l = reservedWords.length; i < l; i++) { + compilerWords[reservedWords[i]] = true; + } + })(); + + JavaScriptCompiler.isValidJavaScriptVariableName = function (name) { + return !JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(name); + }; + + function strictLookup(requireTerminal, compiler, parts, type) { + var stack = compiler.popStack(), + i = 0, + len = parts.length; + if (requireTerminal) { + len--; + } + + for (; i < len; i++) { + stack = compiler.nameLookup(stack, parts[i], type); + } + + if (requireTerminal) { + return [compiler.aliasable('container.strict'), '(', stack, ', ', compiler.quotedString(parts[i]), ')']; + } else { + return stack; + } + } + + exports['default'] = JavaScriptCompiler; + module.exports = exports['default']; + +/***/ }), +/* 43 */ +/***/ (function(module, exports, __webpack_require__) { + + /* global define */ + 'use strict'; + + exports.__esModule = true; + + var _utils = __webpack_require__(5); + + var SourceNode = undefined; + + try { + /* istanbul ignore next */ + if (false) { + // We don't support this in AMD environments. For these environments, we asusme that + // they are running on the browser and thus have no need for the source-map library. + var SourceMap = require('source-map'); + SourceNode = SourceMap.SourceNode; + } + } catch (err) {} + /* NOP */ + + /* istanbul ignore if: tested but not covered in istanbul due to dist build */ + if (!SourceNode) { + SourceNode = function (line, column, srcFile, chunks) { + this.src = ''; + if (chunks) { + this.add(chunks); + } + }; + /* istanbul ignore next */ + SourceNode.prototype = { + add: function add(chunks) { + if (_utils.isArray(chunks)) { + chunks = chunks.join(''); + } + this.src += chunks; + }, + prepend: function prepend(chunks) { + if (_utils.isArray(chunks)) { + chunks = chunks.join(''); + } + this.src = chunks + this.src; + }, + toStringWithSourceMap: function toStringWithSourceMap() { + return { code: this.toString() }; + }, + toString: function toString() { + return this.src; + } + }; + } + + function castChunk(chunk, codeGen, loc) { + if (_utils.isArray(chunk)) { + var ret = []; + + for (var i = 0, len = chunk.length; i < len; i++) { + ret.push(codeGen.wrap(chunk[i], loc)); + } + return ret; + } else if (typeof chunk === 'boolean' || typeof chunk === 'number') { + // Handle primitives that the SourceNode will throw up on + return chunk + ''; + } + return chunk; + } + + function CodeGen(srcFile) { + this.srcFile = srcFile; + this.source = []; + } + + CodeGen.prototype = { + isEmpty: function isEmpty() { + return !this.source.length; + }, + prepend: function prepend(source, loc) { + this.source.unshift(this.wrap(source, loc)); + }, + push: function push(source, loc) { + this.source.push(this.wrap(source, loc)); + }, + + merge: function merge() { + var source = this.empty(); + this.each(function (line) { + source.add([' ', line, '\n']); + }); + return source; + }, + + each: function each(iter) { + for (var i = 0, len = this.source.length; i < len; i++) { + iter(this.source[i]); + } + }, + + empty: function empty() { + var loc = this.currentLocation || { start: {} }; + return new SourceNode(loc.start.line, loc.start.column, this.srcFile); + }, + wrap: function wrap(chunk) { + var loc = arguments.length <= 1 || arguments[1] === undefined ? this.currentLocation || { start: {} } : arguments[1]; + + if (chunk instanceof SourceNode) { + return chunk; + } + + chunk = castChunk(chunk, this, loc); + + return new SourceNode(loc.start.line, loc.start.column, this.srcFile, chunk); + }, + + functionCall: function functionCall(fn, type, params) { + params = this.generateList(params); + return this.wrap([fn, type ? '.' + type + '(' : '(', params, ')']); + }, + + quotedString: function quotedString(str) { + return '"' + (str + '').replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 + .replace(/\u2029/g, '\\u2029') + '"'; + }, + + objectLiteral: function objectLiteral(obj) { + var pairs = []; + + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + var value = castChunk(obj[key], this); + if (value !== 'undefined') { + pairs.push([this.quotedString(key), ':', value]); + } + } + } + + var ret = this.generateList(pairs); + ret.prepend('{'); + ret.add('}'); + return ret; + }, + + generateList: function generateList(entries) { + var ret = this.empty(); + + for (var i = 0, len = entries.length; i < len; i++) { + if (i) { + ret.add(','); + } + + ret.add(castChunk(entries[i], this)); + } + + return ret; + }, + + generateArray: function generateArray(entries) { + var ret = this.generateList(entries); + ret.prepend('['); + ret.add(']'); + + return ret; + } + }; + + exports['default'] = CodeGen; + module.exports = exports['default']; + +/***/ }) +/******/ ]) +}); +; \ No newline at end of file diff --git a/static/js/typeahead.js b/static/js/typeahead.js new file mode 100644 index 00000000..14f97d68 --- /dev/null +++ b/static/js/typeahead.js @@ -0,0 +1,2451 @@ +/*! + * typeahead.js 0.11.1 + * https://github.com/twitter/typeahead.js + * Copyright 2013-2015 Twitter, Inc. and other contributors; Licensed MIT + */ + +(function(root, factory) { + if (typeof define === "function" && define.amd) { + define("bloodhound", [ "jquery" ], function(a0) { + return root["Bloodhound"] = factory(a0); + }); + } else if (typeof exports === "object") { + module.exports = factory(require("jquery")); + } else { + root["Bloodhound"] = factory(jQuery); + } +})(this, function($) { + var _ = function() { + "use strict"; + return { + isMsie: function() { + return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; + }, + isBlankString: function(str) { + return !str || /^\s*$/.test(str); + }, + escapeRegExChars: function(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + }, + isString: function(obj) { + return typeof obj === "string"; + }, + isNumber: function(obj) { + return typeof obj === "number"; + }, + isArray: $.isArray, + isFunction: $.isFunction, + isObject: $.isPlainObject, + isUndefined: function(obj) { + return typeof obj === "undefined"; + }, + isElement: function(obj) { + return !!(obj && obj.nodeType === 1); + }, + isJQuery: function(obj) { + return obj instanceof $; + }, + toStr: function toStr(s) { + return _.isUndefined(s) || s === null ? "" : s + ""; + }, + bind: $.proxy, + each: function(collection, cb) { + $.each(collection, reverseArgs); + function reverseArgs(index, value) { + return cb(value, index); + } + }, + map: $.map, + filter: $.grep, + every: function(obj, test) { + var result = true; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (!(result = test.call(null, val, key, obj))) { + return false; + } + }); + return !!result; + }, + some: function(obj, test) { + var result = false; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (result = test.call(null, val, key, obj)) { + return false; + } + }); + return !!result; + }, + mixin: $.extend, + identity: function(x) { + return x; + }, + clone: function(obj) { + return $.extend(true, {}, obj); + }, + getIdGenerator: function() { + var counter = 0; + return function() { + return counter++; + }; + }, + templatify: function templatify(obj) { + return $.isFunction(obj) ? obj : template; + function template() { + return String(obj); + } + }, + defer: function(fn) { + setTimeout(fn, 0); + }, + debounce: function(func, wait, immediate) { + var timeout, result; + return function() { + var context = this, args = arguments, later, callNow; + later = function() { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + } + }; + callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + } + return result; + }; + }, + throttle: function(func, wait) { + var context, args, timeout, result, previous, later; + previous = 0; + later = function() { + previous = new Date(); + timeout = null; + result = func.apply(context, args); + }; + return function() { + var now = new Date(), remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }, + stringify: function(val) { + return _.isString(val) ? val : JSON.stringify(val); + }, + noop: function() {} + }; + }(); + var VERSION = "0.11.1"; + var tokenizers = function() { + "use strict"; + return { + nonword: nonword, + whitespace: whitespace, + obj: { + nonword: getObjTokenizer(nonword), + whitespace: getObjTokenizer(whitespace) + } + }; + function whitespace(str) { + str = _.toStr(str); + return str ? str.split(/\s+/) : []; + } + function nonword(str) { + str = _.toStr(str); + return str ? str.split(/\W+/) : []; + } + function getObjTokenizer(tokenizer) { + return function setKey(keys) { + keys = _.isArray(keys) ? keys : [].slice.call(arguments, 0); + return function tokenize(o) { + var tokens = []; + _.each(keys, function(k) { + tokens = tokens.concat(tokenizer(_.toStr(o[k]))); + }); + return tokens; + }; + }; + } + }(); + var LruCache = function() { + "use strict"; + function LruCache(maxSize) { + this.maxSize = _.isNumber(maxSize) ? maxSize : 100; + this.reset(); + if (this.maxSize <= 0) { + this.set = this.get = $.noop; + } + } + _.mixin(LruCache.prototype, { + set: function set(key, val) { + var tailItem = this.list.tail, node; + if (this.size >= this.maxSize) { + this.list.remove(tailItem); + delete this.hash[tailItem.key]; + this.size--; + } + if (node = this.hash[key]) { + node.val = val; + this.list.moveToFront(node); + } else { + node = new Node(key, val); + this.list.add(node); + this.hash[key] = node; + this.size++; + } + }, + get: function get(key) { + var node = this.hash[key]; + if (node) { + this.list.moveToFront(node); + return node.val; + } + }, + reset: function reset() { + this.size = 0; + this.hash = {}; + this.list = new List(); + } + }); + function List() { + this.head = this.tail = null; + } + _.mixin(List.prototype, { + add: function add(node) { + if (this.head) { + node.next = this.head; + this.head.prev = node; + } + this.head = node; + this.tail = this.tail || node; + }, + remove: function remove(node) { + node.prev ? node.prev.next = node.next : this.head = node.next; + node.next ? node.next.prev = node.prev : this.tail = node.prev; + }, + moveToFront: function(node) { + this.remove(node); + this.add(node); + } + }); + function Node(key, val) { + this.key = key; + this.val = val; + this.prev = this.next = null; + } + return LruCache; + }(); + var PersistentStorage = function() { + "use strict"; + var LOCAL_STORAGE; + try { + LOCAL_STORAGE = window.localStorage; + LOCAL_STORAGE.setItem("~~~", "!"); + LOCAL_STORAGE.removeItem("~~~"); + } catch (err) { + LOCAL_STORAGE = null; + } + function PersistentStorage(namespace, override) { + this.prefix = [ "__", namespace, "__" ].join(""); + this.ttlKey = "__ttl__"; + this.keyMatcher = new RegExp("^" + _.escapeRegExChars(this.prefix)); + this.ls = override || LOCAL_STORAGE; + !this.ls && this._noop(); + } + _.mixin(PersistentStorage.prototype, { + _prefix: function(key) { + return this.prefix + key; + }, + _ttlKey: function(key) { + return this._prefix(key) + this.ttlKey; + }, + _noop: function() { + this.get = this.set = this.remove = this.clear = this.isExpired = _.noop; + }, + _safeSet: function(key, val) { + try { + this.ls.setItem(key, val); + } catch (err) { + if (err.name === "QuotaExceededError") { + this.clear(); + this._noop(); + } + } + }, + get: function(key) { + if (this.isExpired(key)) { + this.remove(key); + } + return decode(this.ls.getItem(this._prefix(key))); + }, + set: function(key, val, ttl) { + if (_.isNumber(ttl)) { + this._safeSet(this._ttlKey(key), encode(now() + ttl)); + } else { + this.ls.removeItem(this._ttlKey(key)); + } + return this._safeSet(this._prefix(key), encode(val)); + }, + remove: function(key) { + this.ls.removeItem(this._ttlKey(key)); + this.ls.removeItem(this._prefix(key)); + return this; + }, + clear: function() { + var i, keys = gatherMatchingKeys(this.keyMatcher); + for (i = keys.length; i--; ) { + this.remove(keys[i]); + } + return this; + }, + isExpired: function(key) { + var ttl = decode(this.ls.getItem(this._ttlKey(key))); + return _.isNumber(ttl) && now() > ttl ? true : false; + } + }); + return PersistentStorage; + function now() { + return new Date().getTime(); + } + function encode(val) { + return JSON.stringify(_.isUndefined(val) ? null : val); + } + function decode(val) { + return $.parseJSON(val); + } + function gatherMatchingKeys(keyMatcher) { + var i, key, keys = [], len = LOCAL_STORAGE.length; + for (i = 0; i < len; i++) { + if ((key = LOCAL_STORAGE.key(i)).match(keyMatcher)) { + keys.push(key.replace(keyMatcher, "")); + } + } + return keys; + } + }(); + var Transport = function() { + "use strict"; + var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, sharedCache = new LruCache(10); + function Transport(o) { + o = o || {}; + this.cancelled = false; + this.lastReq = null; + this._send = o.transport; + this._get = o.limiter ? o.limiter(this._get) : this._get; + this._cache = o.cache === false ? new LruCache(0) : sharedCache; + } + Transport.setMaxPendingRequests = function setMaxPendingRequests(num) { + maxPendingRequests = num; + }; + Transport.resetCache = function resetCache() { + sharedCache.reset(); + }; + _.mixin(Transport.prototype, { + _fingerprint: function fingerprint(o) { + o = o || {}; + return o.url + o.type + $.param(o.data || {}); + }, + _get: function(o, cb) { + var that = this, fingerprint, jqXhr; + fingerprint = this._fingerprint(o); + if (this.cancelled || fingerprint !== this.lastReq) { + return; + } + if (jqXhr = pendingRequests[fingerprint]) { + jqXhr.done(done).fail(fail); + } else if (pendingRequestsCount < maxPendingRequests) { + pendingRequestsCount++; + pendingRequests[fingerprint] = this._send(o).done(done).fail(fail).always(always); + } else { + this.onDeckRequestArgs = [].slice.call(arguments, 0); + } + function done(resp) { + cb(null, resp); + that._cache.set(fingerprint, resp); + } + function fail() { + cb(true); + } + function always() { + pendingRequestsCount--; + delete pendingRequests[fingerprint]; + if (that.onDeckRequestArgs) { + that._get.apply(that, that.onDeckRequestArgs); + that.onDeckRequestArgs = null; + } + } + }, + get: function(o, cb) { + var resp, fingerprint; + cb = cb || $.noop; + o = _.isString(o) ? { + url: o + } : o || {}; + fingerprint = this._fingerprint(o); + this.cancelled = false; + this.lastReq = fingerprint; + if (resp = this._cache.get(fingerprint)) { + cb(null, resp); + } else { + this._get(o, cb); + } + }, + cancel: function() { + this.cancelled = true; + } + }); + return Transport; + }(); + var SearchIndex = window.SearchIndex = function() { + "use strict"; + var CHILDREN = "c", IDS = "i"; + function SearchIndex(o) { + o = o || {}; + if (!o.datumTokenizer || !o.queryTokenizer) { + $.error("datumTokenizer and queryTokenizer are both required"); + } + this.identify = o.identify || _.stringify; + this.datumTokenizer = o.datumTokenizer; + this.queryTokenizer = o.queryTokenizer; + this.reset(); + } + _.mixin(SearchIndex.prototype, { + bootstrap: function bootstrap(o) { + this.datums = o.datums; + this.trie = o.trie; + }, + add: function(data) { + var that = this; + data = _.isArray(data) ? data : [ data ]; + _.each(data, function(datum) { + var id, tokens; + that.datums[id = that.identify(datum)] = datum; + tokens = normalizeTokens(that.datumTokenizer(datum)); + _.each(tokens, function(token) { + var node, chars, ch; + node = that.trie; + chars = token.split(""); + while (ch = chars.shift()) { + node = node[CHILDREN][ch] || (node[CHILDREN][ch] = newNode()); + node[IDS].push(id); + } + }); + }); + }, + get: function get(ids) { + var that = this; + return _.map(ids, function(id) { + return that.datums[id]; + }); + }, + search: function search(query) { + var that = this, tokens, matches; + tokens = normalizeTokens(this.queryTokenizer(query)); + _.each(tokens, function(token) { + var node, chars, ch, ids; + if (matches && matches.length === 0) { + return false; + } + node = that.trie; + chars = token.split(""); + while (node && (ch = chars.shift())) { + node = node[CHILDREN][ch]; + } + if (node && chars.length === 0) { + ids = node[IDS].slice(0); + matches = matches ? getIntersection(matches, ids) : ids; + } else { + matches = []; + return false; + } + }); + return matches ? _.map(unique(matches), function(id) { + return that.datums[id]; + }) : []; + }, + all: function all() { + var values = []; + for (var key in this.datums) { + values.push(this.datums[key]); + } + return values; + }, + reset: function reset() { + this.datums = {}; + this.trie = newNode(); + }, + serialize: function serialize() { + return { + datums: this.datums, + trie: this.trie + }; + } + }); + return SearchIndex; + function normalizeTokens(tokens) { + tokens = _.filter(tokens, function(token) { + return !!token; + }); + tokens = _.map(tokens, function(token) { + return token.toLowerCase(); + }); + return tokens; + } + function newNode() { + var node = {}; + node[IDS] = []; + node[CHILDREN] = {}; + return node; + } + function unique(array) { + var seen = {}, uniques = []; + for (var i = 0, len = array.length; i < len; i++) { + if (!seen[array[i]]) { + seen[array[i]] = true; + uniques.push(array[i]); + } + } + return uniques; + } + function getIntersection(arrayA, arrayB) { + var ai = 0, bi = 0, intersection = []; + arrayA = arrayA.sort(); + arrayB = arrayB.sort(); + var lenArrayA = arrayA.length, lenArrayB = arrayB.length; + while (ai < lenArrayA && bi < lenArrayB) { + if (arrayA[ai] < arrayB[bi]) { + ai++; + } else if (arrayA[ai] > arrayB[bi]) { + bi++; + } else { + intersection.push(arrayA[ai]); + ai++; + bi++; + } + } + return intersection; + } + }(); + var Prefetch = function() { + "use strict"; + var keys; + keys = { + data: "data", + protocol: "protocol", + thumbprint: "thumbprint" + }; + function Prefetch(o) { + this.url = o.url; + this.ttl = o.ttl; + this.cache = o.cache; + this.prepare = o.prepare; + this.transform = o.transform; + this.transport = o.transport; + this.thumbprint = o.thumbprint; + this.storage = new PersistentStorage(o.cacheKey); + } + _.mixin(Prefetch.prototype, { + _settings: function settings() { + return { + url: this.url, + type: "GET", + dataType: "json" + }; + }, + store: function store(data) { + if (!this.cache) { + return; + } + this.storage.set(keys.data, data, this.ttl); + this.storage.set(keys.protocol, location.protocol, this.ttl); + this.storage.set(keys.thumbprint, this.thumbprint, this.ttl); + }, + fromCache: function fromCache() { + var stored = {}, isExpired; + if (!this.cache) { + return null; + } + stored.data = this.storage.get(keys.data); + stored.protocol = this.storage.get(keys.protocol); + stored.thumbprint = this.storage.get(keys.thumbprint); + isExpired = stored.thumbprint !== this.thumbprint || stored.protocol !== location.protocol; + return stored.data && !isExpired ? stored.data : null; + }, + fromNetwork: function(cb) { + var that = this, settings; + if (!cb) { + return; + } + settings = this.prepare(this._settings()); + this.transport(settings).fail(onError).done(onResponse); + function onError() { + cb(true); + } + function onResponse(resp) { + cb(null, that.transform(resp)); + } + }, + clear: function clear() { + this.storage.clear(); + return this; + } + }); + return Prefetch; + }(); + var Remote = function() { + "use strict"; + function Remote(o) { + this.url = o.url; + this.prepare = o.prepare; + this.transform = o.transform; + this.transport = new Transport({ + cache: o.cache, + limiter: o.limiter, + transport: o.transport + }); + } + _.mixin(Remote.prototype, { + _settings: function settings() { + return { + url: this.url, + type: "GET", + dataType: "json" + }; + }, + get: function get(query, cb) { + var that = this, settings; + if (!cb) { + return; + } + query = query || ""; + settings = this.prepare(query, this._settings()); + return this.transport.get(settings, onResponse); + function onResponse(err, resp) { + err ? cb([]) : cb(that.transform(resp)); + } + }, + cancelLastRequest: function cancelLastRequest() { + this.transport.cancel(); + } + }); + return Remote; + }(); + var oParser = function() { + "use strict"; + return function parse(o) { + var defaults, sorter; + defaults = { + initialize: true, + identify: _.stringify, + datumTokenizer: null, + queryTokenizer: null, + sufficient: 5, + sorter: null, + local: [], + prefetch: null, + remote: null + }; + o = _.mixin(defaults, o || {}); + !o.datumTokenizer && $.error("datumTokenizer is required"); + !o.queryTokenizer && $.error("queryTokenizer is required"); + sorter = o.sorter; + o.sorter = sorter ? function(x) { + return x.sort(sorter); + } : _.identity; + o.local = _.isFunction(o.local) ? o.local() : o.local; + o.prefetch = parsePrefetch(o.prefetch); + o.remote = parseRemote(o.remote); + return o; + }; + function parsePrefetch(o) { + var defaults; + if (!o) { + return null; + } + defaults = { + url: null, + ttl: 24 * 60 * 60 * 1e3, + cache: true, + cacheKey: null, + thumbprint: "", + prepare: _.identity, + transform: _.identity, + transport: null + }; + o = _.isString(o) ? { + url: o + } : o; + o = _.mixin(defaults, o); + !o.url && $.error("prefetch requires url to be set"); + o.transform = o.filter || o.transform; + o.cacheKey = o.cacheKey || o.url; + o.thumbprint = VERSION + o.thumbprint; + o.transport = o.transport ? callbackToDeferred(o.transport) : $.ajax; + return o; + } + function parseRemote(o) { + var defaults; + if (!o) { + return; + } + defaults = { + url: null, + cache: true, + prepare: null, + replace: null, + wildcard: null, + limiter: null, + rateLimitBy: "debounce", + rateLimitWait: 300, + transform: _.identity, + transport: null + }; + o = _.isString(o) ? { + url: o + } : o; + o = _.mixin(defaults, o); + !o.url && $.error("remote requires url to be set"); + o.transform = o.filter || o.transform; + o.prepare = toRemotePrepare(o); + o.limiter = toLimiter(o); + o.transport = o.transport ? callbackToDeferred(o.transport) : $.ajax; + delete o.replace; + delete o.wildcard; + delete o.rateLimitBy; + delete o.rateLimitWait; + return o; + } + function toRemotePrepare(o) { + var prepare, replace, wildcard; + prepare = o.prepare; + replace = o.replace; + wildcard = o.wildcard; + if (prepare) { + return prepare; + } + if (replace) { + prepare = prepareByReplace; + } else if (o.wildcard) { + prepare = prepareByWildcard; + } else { + prepare = idenityPrepare; + } + return prepare; + function prepareByReplace(query, settings) { + settings.url = replace(settings.url, query); + return settings; + } + function prepareByWildcard(query, settings) { + settings.url = settings.url.replace(wildcard, encodeURIComponent(query)); + return settings; + } + function idenityPrepare(query, settings) { + return settings; + } + } + function toLimiter(o) { + var limiter, method, wait; + limiter = o.limiter; + method = o.rateLimitBy; + wait = o.rateLimitWait; + if (!limiter) { + limiter = /^throttle$/i.test(method) ? throttle(wait) : debounce(wait); + } + return limiter; + function debounce(wait) { + return function debounce(fn) { + return _.debounce(fn, wait); + }; + } + function throttle(wait) { + return function throttle(fn) { + return _.throttle(fn, wait); + }; + } + } + function callbackToDeferred(fn) { + return function wrapper(o) { + var deferred = $.Deferred(); + fn(o, onSuccess, onError); + return deferred; + function onSuccess(resp) { + _.defer(function() { + deferred.resolve(resp); + }); + } + function onError(err) { + _.defer(function() { + deferred.reject(err); + }); + } + }; + } + }(); + var Bloodhound = function() { + "use strict"; + var old; + old = window && window.Bloodhound; + function Bloodhound(o) { + o = oParser(o); + this.sorter = o.sorter; + this.identify = o.identify; + this.sufficient = o.sufficient; + this.local = o.local; + this.remote = o.remote ? new Remote(o.remote) : null; + this.prefetch = o.prefetch ? new Prefetch(o.prefetch) : null; + this.index = new SearchIndex({ + identify: this.identify, + datumTokenizer: o.datumTokenizer, + queryTokenizer: o.queryTokenizer + }); + o.initialize !== false && this.initialize(); + } + Bloodhound.noConflict = function noConflict() { + window && (window.Bloodhound = old); + return Bloodhound; + }; + Bloodhound.tokenizers = tokenizers; + _.mixin(Bloodhound.prototype, { + __ttAdapter: function ttAdapter() { + var that = this; + return this.remote ? withAsync : withoutAsync; + function withAsync(query, sync, async) { + return that.search(query, sync, async); + } + function withoutAsync(query, sync) { + return that.search(query, sync); + } + }, + _loadPrefetch: function loadPrefetch() { + var that = this, deferred, serialized; + deferred = $.Deferred(); + if (!this.prefetch) { + deferred.resolve(); + } else if (serialized = this.prefetch.fromCache()) { + this.index.bootstrap(serialized); + deferred.resolve(); + } else { + this.prefetch.fromNetwork(done); + } + return deferred.promise(); + function done(err, data) { + if (err) { + return deferred.reject(); + } + that.add(data); + that.prefetch.store(that.index.serialize()); + deferred.resolve(); + } + }, + _initialize: function initialize() { + var that = this, deferred; + this.clear(); + (this.initPromise = this._loadPrefetch()).done(addLocalToIndex); + return this.initPromise; + function addLocalToIndex() { + that.add(that.local); + } + }, + initialize: function initialize(force) { + return !this.initPromise || force ? this._initialize() : this.initPromise; + }, + add: function add(data) { + this.index.add(data); + return this; + }, + get: function get(ids) { + ids = _.isArray(ids) ? ids : [].slice.call(arguments); + return this.index.get(ids); + }, + search: function search(query, sync, async) { + var that = this, local; + local = this.sorter(this.index.search(query)); + sync(this.remote ? local.slice() : local); + if (this.remote && local.length < this.sufficient) { + this.remote.get(query, processRemote); + } else if (this.remote) { + this.remote.cancelLastRequest(); + } + return this; + function processRemote(remote) { + var nonDuplicates = []; + _.each(remote, function(r) { + !_.some(local, function(l) { + return that.identify(r) === that.identify(l); + }) && nonDuplicates.push(r); + }); + async && async(nonDuplicates); + } + }, + all: function all() { + return this.index.all(); + }, + clear: function clear() { + this.index.reset(); + return this; + }, + clearPrefetchCache: function clearPrefetchCache() { + this.prefetch && this.prefetch.clear(); + return this; + }, + clearRemoteCache: function clearRemoteCache() { + Transport.resetCache(); + return this; + }, + ttAdapter: function ttAdapter() { + return this.__ttAdapter(); + } + }); + return Bloodhound; + }(); + return Bloodhound; +}); + +(function(root, factory) { + if (typeof define === "function" && define.amd) { + define("typeahead.js", [ "jquery" ], function(a0) { + return factory(a0); + }); + } else if (typeof exports === "object") { + module.exports = factory(require("jquery")); + } else { + factory(jQuery); + } +})(this, function($) { + var _ = function() { + "use strict"; + return { + isMsie: function() { + return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; + }, + isBlankString: function(str) { + return !str || /^\s*$/.test(str); + }, + escapeRegExChars: function(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + }, + isString: function(obj) { + return typeof obj === "string"; + }, + isNumber: function(obj) { + return typeof obj === "number"; + }, + isArray: $.isArray, + isFunction: $.isFunction, + isObject: $.isPlainObject, + isUndefined: function(obj) { + return typeof obj === "undefined"; + }, + isElement: function(obj) { + return !!(obj && obj.nodeType === 1); + }, + isJQuery: function(obj) { + return obj instanceof $; + }, + toStr: function toStr(s) { + return _.isUndefined(s) || s === null ? "" : s + ""; + }, + bind: $.proxy, + each: function(collection, cb) { + $.each(collection, reverseArgs); + function reverseArgs(index, value) { + return cb(value, index); + } + }, + map: $.map, + filter: $.grep, + every: function(obj, test) { + var result = true; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (!(result = test.call(null, val, key, obj))) { + return false; + } + }); + return !!result; + }, + some: function(obj, test) { + var result = false; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (result = test.call(null, val, key, obj)) { + return false; + } + }); + return !!result; + }, + mixin: $.extend, + identity: function(x) { + return x; + }, + clone: function(obj) { + return $.extend(true, {}, obj); + }, + getIdGenerator: function() { + var counter = 0; + return function() { + return counter++; + }; + }, + templatify: function templatify(obj) { + return $.isFunction(obj) ? obj : template; + function template() { + return String(obj); + } + }, + defer: function(fn) { + setTimeout(fn, 0); + }, + debounce: function(func, wait, immediate) { + var timeout, result; + return function() { + var context = this, args = arguments, later, callNow; + later = function() { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + } + }; + callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + } + return result; + }; + }, + throttle: function(func, wait) { + var context, args, timeout, result, previous, later; + previous = 0; + later = function() { + previous = new Date(); + timeout = null; + result = func.apply(context, args); + }; + return function() { + var now = new Date(), remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }, + stringify: function(val) { + return _.isString(val) ? val : JSON.stringify(val); + }, + noop: function() {} + }; + }(); + var WWW = function() { + "use strict"; + var defaultClassNames = { + wrapper: "twitter-typeahead", + input: "tt-input", + hint: "tt-hint", + menu: "tt-menu", + dataset: "tt-dataset", + suggestion: "tt-suggestion", + selectable: "tt-selectable", + empty: "tt-empty", + open: "tt-open", + cursor: "tt-cursor", + highlight: "tt-highlight" + }; + return build; + function build(o) { + var www, classes; + classes = _.mixin({}, defaultClassNames, o); + www = { + css: buildCss(), + classes: classes, + html: buildHtml(classes), + selectors: buildSelectors(classes) + }; + return { + css: www.css, + html: www.html, + classes: www.classes, + selectors: www.selectors, + mixin: function(o) { + _.mixin(o, www); + } + }; + } + function buildHtml(c) { + return { + wrapper: '', + menu: '
' + }; + } + function buildSelectors(classes) { + var selectors = {}; + _.each(classes, function(v, k) { + selectors[k] = "." + v; + }); + return selectors; + } + function buildCss() { + var css = { + wrapper: { + position: "relative", + display: "inline-block" + }, + hint: { + position: "absolute", + top: "0", + left: "0", + borderColor: "transparent", + boxShadow: "none", + opacity: "1" + }, + input: { + position: "relative", + verticalAlign: "top", + backgroundColor: "transparent" + }, + inputWithNoHint: { + position: "relative", + verticalAlign: "top" + }, + menu: { + position: "absolute", + top: "100%", + left: "0", + zIndex: "100", + display: "none" + }, + ltr: { + left: "0", + right: "auto" + }, + rtl: { + left: "auto", + right: " 0" + } + }; + if (_.isMsie()) { + _.mixin(css.input, { + backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" + }); + } + return css; + } + }(); + var EventBus = function() { + "use strict"; + var namespace, deprecationMap; + namespace = "typeahead:"; + deprecationMap = { + render: "rendered", + cursorchange: "cursorchanged", + select: "selected", + autocomplete: "autocompleted" + }; + function EventBus(o) { + if (!o || !o.el) { + $.error("EventBus initialized without el"); + } + this.$el = $(o.el); + } + _.mixin(EventBus.prototype, { + _trigger: function(type, args) { + var $e; + $e = $.Event(namespace + type); + (args = args || []).unshift($e); + this.$el.trigger.apply(this.$el, args); + return $e; + }, + before: function(type) { + var args, $e; + args = [].slice.call(arguments, 1); + $e = this._trigger("before" + type, args); + return $e.isDefaultPrevented(); + }, + trigger: function(type) { + var deprecatedType; + this._trigger(type, [].slice.call(arguments, 1)); + if (deprecatedType = deprecationMap[type]) { + this._trigger(deprecatedType, [].slice.call(arguments, 1)); + } + } + }); + return EventBus; + }(); + var EventEmitter = function() { + "use strict"; + var splitter = /\s+/, nextTick = getNextTick(); + return { + onSync: onSync, + onAsync: onAsync, + off: off, + trigger: trigger + }; + function on(method, types, cb, context) { + var type; + if (!cb) { + return this; + } + types = types.split(splitter); + cb = context ? bindContext(cb, context) : cb; + this._callbacks = this._callbacks || {}; + while (type = types.shift()) { + this._callbacks[type] = this._callbacks[type] || { + sync: [], + async: [] + }; + this._callbacks[type][method].push(cb); + } + return this; + } + function onAsync(types, cb, context) { + return on.call(this, "async", types, cb, context); + } + function onSync(types, cb, context) { + return on.call(this, "sync", types, cb, context); + } + function off(types) { + var type; + if (!this._callbacks) { + return this; + } + types = types.split(splitter); + while (type = types.shift()) { + delete this._callbacks[type]; + } + return this; + } + function trigger(types) { + var type, callbacks, args, syncFlush, asyncFlush; + if (!this._callbacks) { + return this; + } + types = types.split(splitter); + args = [].slice.call(arguments, 1); + while ((type = types.shift()) && (callbacks = this._callbacks[type])) { + syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args)); + asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args)); + syncFlush() && nextTick(asyncFlush); + } + return this; + } + function getFlush(callbacks, context, args) { + return flush; + function flush() { + var cancelled; + for (var i = 0, len = callbacks.length; !cancelled && i < len; i += 1) { + cancelled = callbacks[i].apply(context, args) === false; + } + return !cancelled; + } + } + function getNextTick() { + var nextTickFn; + if (window.setImmediate) { + nextTickFn = function nextTickSetImmediate(fn) { + setImmediate(function() { + fn(); + }); + }; + } else { + nextTickFn = function nextTickSetTimeout(fn) { + setTimeout(function() { + fn(); + }, 0); + }; + } + return nextTickFn; + } + function bindContext(fn, context) { + return fn.bind ? fn.bind(context) : function() { + fn.apply(context, [].slice.call(arguments, 0)); + }; + } + }(); + var highlight = function(doc) { + "use strict"; + var defaults = { + node: null, + pattern: null, + tagName: "strong", + className: null, + wordsOnly: false, + caseSensitive: false + }; + return function hightlight(o) { + var regex; + o = _.mixin({}, defaults, o); + if (!o.node || !o.pattern) { + return; + } + o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ]; + regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly); + traverse(o.node, hightlightTextNode); + function hightlightTextNode(textNode) { + var match, patternNode, wrapperNode; + if (match = regex.exec(textNode.data)) { + wrapperNode = doc.createElement(o.tagName); + o.className && (wrapperNode.className = o.className); + patternNode = textNode.splitText(match.index); + patternNode.splitText(match[0].length); + wrapperNode.appendChild(patternNode.cloneNode(true)); + textNode.parentNode.replaceChild(wrapperNode, patternNode); + } + return !!match; + } + function traverse(el, hightlightTextNode) { + var childNode, TEXT_NODE_TYPE = 3; + for (var i = 0; i < el.childNodes.length; i++) { + childNode = el.childNodes[i]; + if (childNode.nodeType === TEXT_NODE_TYPE) { + i += hightlightTextNode(childNode) ? 1 : 0; + } else { + traverse(childNode, hightlightTextNode); + } + } + } + }; + function getRegex(patterns, caseSensitive, wordsOnly) { + var escapedPatterns = [], regexStr; + for (var i = 0, len = patterns.length; i < len; i++) { + escapedPatterns.push(_.escapeRegExChars(patterns[i])); + } + regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")"; + return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i"); + } + }(window.document); + var Input = function() { + "use strict"; + var specialKeyCodeMap; + specialKeyCodeMap = { + 9: "tab", + 27: "esc", + 37: "left", + 39: "right", + 13: "enter", + 38: "up", + 40: "down" + }; + function Input(o, www) { + o = o || {}; + if (!o.input) { + $.error("input is missing"); + } + www.mixin(this); + this.$hint = $(o.hint); + this.$input = $(o.input); + this.query = this.$input.val(); + this.queryWhenFocused = this.hasFocus() ? this.query : null; + this.$overflowHelper = buildOverflowHelper(this.$input); + this._checkLanguageDirection(); + if (this.$hint.length === 0) { + this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop; + } + } + Input.normalizeQuery = function(str) { + return _.toStr(str).replace(/^\s*/g, "").replace(/\s{2,}/g, " "); + }; + _.mixin(Input.prototype, EventEmitter, { + _onBlur: function onBlur() { + this.resetInputValue(); + this.trigger("blurred"); + }, + _onFocus: function onFocus() { + this.queryWhenFocused = this.query; + this.trigger("focused"); + }, + _onKeydown: function onKeydown($e) { + var keyName = specialKeyCodeMap[$e.which || $e.keyCode]; + this._managePreventDefault(keyName, $e); + if (keyName && this._shouldTrigger(keyName, $e)) { + this.trigger(keyName + "Keyed", $e); + } + }, + _onInput: function onInput() { + this._setQuery(this.getInputValue()); + this.clearHintIfInvalid(); + this._checkLanguageDirection(); + }, + _managePreventDefault: function managePreventDefault(keyName, $e) { + var preventDefault; + switch (keyName) { + case "up": + case "down": + preventDefault = !withModifier($e); + break; + + default: + preventDefault = false; + } + preventDefault && $e.preventDefault(); + }, + _shouldTrigger: function shouldTrigger(keyName, $e) { + var trigger; + switch (keyName) { + case "tab": + trigger = !withModifier($e); + break; + + default: + trigger = true; + } + return trigger; + }, + _checkLanguageDirection: function checkLanguageDirection() { + var dir = (this.$input.css("direction") || "ltr").toLowerCase(); + if (this.dir !== dir) { + this.dir = dir; + this.$hint.attr("dir", dir); + this.trigger("langDirChanged", dir); + } + }, + _setQuery: function setQuery(val, silent) { + var areEquivalent, hasDifferentWhitespace; + areEquivalent = areQueriesEquivalent(val, this.query); + hasDifferentWhitespace = areEquivalent ? this.query.length !== val.length : false; + this.query = val; + if (!silent && !areEquivalent) { + this.trigger("queryChanged", this.query); + } else if (!silent && hasDifferentWhitespace) { + this.trigger("whitespaceChanged", this.query); + } + }, + bind: function() { + var that = this, onBlur, onFocus, onKeydown, onInput; + onBlur = _.bind(this._onBlur, this); + onFocus = _.bind(this._onFocus, this); + onKeydown = _.bind(this._onKeydown, this); + onInput = _.bind(this._onInput, this); + this.$input.on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown); + if (!_.isMsie() || _.isMsie() > 9) { + this.$input.on("input.tt", onInput); + } else { + this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { + if (specialKeyCodeMap[$e.which || $e.keyCode]) { + return; + } + _.defer(_.bind(that._onInput, that, $e)); + }); + } + return this; + }, + focus: function focus() { + this.$input.focus(); + }, + blur: function blur() { + this.$input.blur(); + }, + getLangDir: function getLangDir() { + return this.dir; + }, + getQuery: function getQuery() { + return this.query || ""; + }, + setQuery: function setQuery(val, silent) { + this.setInputValue(val); + this._setQuery(val, silent); + }, + hasQueryChangedSinceLastFocus: function hasQueryChangedSinceLastFocus() { + return this.query !== this.queryWhenFocused; + }, + getInputValue: function getInputValue() { + return this.$input.val(); + }, + setInputValue: function setInputValue(value) { + this.$input.val(value); + this.clearHintIfInvalid(); + this._checkLanguageDirection(); + }, + resetInputValue: function resetInputValue() { + this.setInputValue(this.query); + }, + getHint: function getHint() { + return this.$hint.val(); + }, + setHint: function setHint(value) { + this.$hint.val(value); + }, + clearHint: function clearHint() { + this.setHint(""); + }, + clearHintIfInvalid: function clearHintIfInvalid() { + var val, hint, valIsPrefixOfHint, isValid; + val = this.getInputValue(); + hint = this.getHint(); + valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0; + isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow(); + !isValid && this.clearHint(); + }, + hasFocus: function hasFocus() { + return this.$input.is(":focus"); + }, + hasOverflow: function hasOverflow() { + var constraint = this.$input.width() - 2; + this.$overflowHelper.text(this.getInputValue()); + return this.$overflowHelper.width() >= constraint; + }, + isCursorAtEnd: function() { + var valueLength, selectionStart, range; + valueLength = this.$input.val().length; + selectionStart = this.$input[0].selectionStart; + if (_.isNumber(selectionStart)) { + return selectionStart === valueLength; + } else if (document.selection) { + range = document.selection.createRange(); + range.moveStart("character", -valueLength); + return valueLength === range.text.length; + } + return true; + }, + destroy: function destroy() { + this.$hint.off(".tt"); + this.$input.off(".tt"); + this.$overflowHelper.remove(); + this.$hint = this.$input = this.$overflowHelper = $("
"); + } + }); + return Input; + function buildOverflowHelper($input) { + return $('').css({ + position: "absolute", + visibility: "hidden", + whiteSpace: "pre", + fontFamily: $input.css("font-family"), + fontSize: $input.css("font-size"), + fontStyle: $input.css("font-style"), + fontVariant: $input.css("font-variant"), + fontWeight: $input.css("font-weight"), + wordSpacing: $input.css("word-spacing"), + letterSpacing: $input.css("letter-spacing"), + textIndent: $input.css("text-indent"), + textRendering: $input.css("text-rendering"), + textTransform: $input.css("text-transform") + }).insertAfter($input); + } + function areQueriesEquivalent(a, b) { + return Input.normalizeQuery(a) === Input.normalizeQuery(b); + } + function withModifier($e) { + return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey; + } + }(); + var Dataset = function() { + "use strict"; + var keys, nameGenerator; + keys = { + val: "tt-selectable-display", + obj: "tt-selectable-object" + }; + nameGenerator = _.getIdGenerator(); + function Dataset(o, www) { + o = o || {}; + o.templates = o.templates || {}; + o.templates.notFound = o.templates.notFound || o.templates.empty; + if (!o.source) { + $.error("missing source"); + } + if (!o.node) { + $.error("missing node"); + } + if (o.name && !isValidName(o.name)) { + $.error("invalid dataset name: " + o.name); + } + www.mixin(this); + this.highlight = !!o.highlight; + this.name = o.name || nameGenerator(); + this.limit = o.limit || 5; + this.displayFn = getDisplayFn(o.display || o.displayKey); + this.templates = getTemplates(o.templates, this.displayFn); + this.source = o.source.__ttAdapter ? o.source.__ttAdapter() : o.source; + this.async = _.isUndefined(o.async) ? this.source.length > 2 : !!o.async; + this._resetLastSuggestion(); + this.$el = $(o.node).addClass(this.classes.dataset).addClass(this.classes.dataset + "-" + this.name); + } + Dataset.extractData = function extractData(el) { + var $el = $(el); + if ($el.data(keys.obj)) { + return { + val: $el.data(keys.val) || "", + obj: $el.data(keys.obj) || null + }; + } + return null; + }; + _.mixin(Dataset.prototype, EventEmitter, { + _overwrite: function overwrite(query, suggestions) { + suggestions = suggestions || []; + if (suggestions.length) { + this._renderSuggestions(query, suggestions); + } else if (this.async && this.templates.pending) { + this._renderPending(query); + } else if (!this.async && this.templates.notFound) { + this._renderNotFound(query); + } else { + this._empty(); + } + this.trigger("rendered", this.name, suggestions, false); + }, + _append: function append(query, suggestions) { + suggestions = suggestions || []; + if (suggestions.length && this.$lastSuggestion.length) { + this._appendSuggestions(query, suggestions); + } else if (suggestions.length) { + this._renderSuggestions(query, suggestions); + } else if (!this.$lastSuggestion.length && this.templates.notFound) { + this._renderNotFound(query); + } + this.trigger("rendered", this.name, suggestions, true); + }, + _renderSuggestions: function renderSuggestions(query, suggestions) { + var $fragment; + $fragment = this._getSuggestionsFragment(query, suggestions); + this.$lastSuggestion = $fragment.children().last(); + this.$el.html($fragment).prepend(this._getHeader(query, suggestions)).append(this._getFooter(query, suggestions)); + }, + _appendSuggestions: function appendSuggestions(query, suggestions) { + var $fragment, $lastSuggestion; + $fragment = this._getSuggestionsFragment(query, suggestions); + $lastSuggestion = $fragment.children().last(); + this.$lastSuggestion.after($fragment); + this.$lastSuggestion = $lastSuggestion; + }, + _renderPending: function renderPending(query) { + var template = this.templates.pending; + this._resetLastSuggestion(); + template && this.$el.html(template({ + query: query, + dataset: this.name + })); + }, + _renderNotFound: function renderNotFound(query) { + var template = this.templates.notFound; + this._resetLastSuggestion(); + template && this.$el.html(template({ + query: query, + dataset: this.name + })); + }, + _empty: function empty() { + this.$el.empty(); + this._resetLastSuggestion(); + }, + _getSuggestionsFragment: function getSuggestionsFragment(query, suggestions) { + var that = this, fragment; + fragment = document.createDocumentFragment(); + _.each(suggestions, function getSuggestionNode(suggestion) { + var $el, context; + context = that._injectQuery(query, suggestion); + $el = $(that.templates.suggestion(context)).data(keys.obj, suggestion).data(keys.val, that.displayFn(suggestion)).addClass(that.classes.suggestion + " " + that.classes.selectable); + fragment.appendChild($el[0]); + }); + this.highlight && highlight({ + className: this.classes.highlight, + node: fragment, + pattern: query + }); + return $(fragment); + }, + _getFooter: function getFooter(query, suggestions) { + return this.templates.footer ? this.templates.footer({ + query: query, + suggestions: suggestions, + dataset: this.name + }) : null; + }, + _getHeader: function getHeader(query, suggestions) { + return this.templates.header ? this.templates.header({ + query: query, + suggestions: suggestions, + dataset: this.name + }) : null; + }, + _resetLastSuggestion: function resetLastSuggestion() { + this.$lastSuggestion = $(); + }, + _injectQuery: function injectQuery(query, obj) { + return _.isObject(obj) ? _.mixin({ + _query: query + }, obj) : obj; + }, + update: function update(query) { + var that = this, canceled = false, syncCalled = false, rendered = 0; + this.cancel(); + this.cancel = function cancel() { + canceled = true; + that.cancel = $.noop; + that.async && that.trigger("asyncCanceled", query); + }; + this.source(query, sync, async); + !syncCalled && sync([]); + function sync(suggestions) { + if (syncCalled) { + return; + } + syncCalled = true; + suggestions = (suggestions || []).slice(0, that.limit); + rendered = suggestions.length; + that._overwrite(query, suggestions); + if (rendered < that.limit && that.async) { + that.trigger("asyncRequested", query); + } + } + function async(suggestions) { + suggestions = suggestions || []; + if (!canceled && rendered < that.limit) { + that.cancel = $.noop; + rendered += suggestions.length; + that._append(query, suggestions.slice(0, that.limit - rendered)); + that.async && that.trigger("asyncReceived", query); + } + } + }, + cancel: $.noop, + clear: function clear() { + this._empty(); + this.cancel(); + this.trigger("cleared"); + }, + isEmpty: function isEmpty() { + return this.$el.is(":empty"); + }, + destroy: function destroy() { + this.$el = $("
"); + } + }); + return Dataset; + function getDisplayFn(display) { + display = display || _.stringify; + return _.isFunction(display) ? display : displayFn; + function displayFn(obj) { + return obj[display]; + } + } + function getTemplates(templates, displayFn) { + return { + notFound: templates.notFound && _.templatify(templates.notFound), + pending: templates.pending && _.templatify(templates.pending), + header: templates.header && _.templatify(templates.header), + footer: templates.footer && _.templatify(templates.footer), + suggestion: templates.suggestion || suggestionTemplate + }; + function suggestionTemplate(context) { + return $("
").text(displayFn(context)); + } + } + function isValidName(str) { + return /^[_a-zA-Z0-9-]+$/.test(str); + } + }(); + var Menu = function() { + "use strict"; + function Menu(o, www) { + var that = this; + o = o || {}; + if (!o.node) { + $.error("node is required"); + } + www.mixin(this); + this.$node = $(o.node); + this.query = null; + this.datasets = _.map(o.datasets, initializeDataset); + function initializeDataset(oDataset) { + var node = that.$node.find(oDataset.node).first(); + oDataset.node = node.length ? node : $("
").appendTo(that.$node); + return new Dataset(oDataset, www); + } + } + _.mixin(Menu.prototype, EventEmitter, { + _onSelectableClick: function onSelectableClick($e) { + this.trigger("selectableClicked", $($e.currentTarget)); + }, + _onRendered: function onRendered(type, dataset, suggestions, async) { + this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); + this.trigger("datasetRendered", dataset, suggestions, async); + }, + _onCleared: function onCleared() { + this.$node.toggleClass(this.classes.empty, this._allDatasetsEmpty()); + this.trigger("datasetCleared"); + }, + _propagate: function propagate() { + this.trigger.apply(this, arguments); + }, + _allDatasetsEmpty: function allDatasetsEmpty() { + return _.every(this.datasets, isDatasetEmpty); + function isDatasetEmpty(dataset) { + return dataset.isEmpty(); + } + }, + _getSelectables: function getSelectables() { + return this.$node.find(this.selectors.selectable); + }, + _removeCursor: function _removeCursor() { + var $selectable = this.getActiveSelectable(); + $selectable && $selectable.removeClass(this.classes.cursor); + }, + _ensureVisible: function ensureVisible($el) { + var elTop, elBottom, nodeScrollTop, nodeHeight; + elTop = $el.position().top; + elBottom = elTop + $el.outerHeight(true); + nodeScrollTop = this.$node.scrollTop(); + nodeHeight = this.$node.height() + parseInt(this.$node.css("paddingTop"), 10) + parseInt(this.$node.css("paddingBottom"), 10); + if (elTop < 0) { + this.$node.scrollTop(nodeScrollTop + elTop); + } else if (nodeHeight < elBottom) { + this.$node.scrollTop(nodeScrollTop + (elBottom - nodeHeight)); + } + }, + bind: function() { + var that = this, onSelectableClick; + onSelectableClick = _.bind(this._onSelectableClick, this); + this.$node.on("click.tt", this.selectors.selectable, onSelectableClick); + _.each(this.datasets, function(dataset) { + dataset.onSync("asyncRequested", that._propagate, that).onSync("asyncCanceled", that._propagate, that).onSync("asyncReceived", that._propagate, that).onSync("rendered", that._onRendered, that).onSync("cleared", that._onCleared, that); + }); + return this; + }, + isOpen: function isOpen() { + return this.$node.hasClass(this.classes.open); + }, + open: function open() { + this.$node.addClass(this.classes.open); + }, + close: function close() { + this.$node.removeClass(this.classes.open); + this._removeCursor(); + }, + setLanguageDirection: function setLanguageDirection(dir) { + this.$node.attr("dir", dir); + }, + selectableRelativeToCursor: function selectableRelativeToCursor(delta) { + var $selectables, $oldCursor, oldIndex, newIndex; + $oldCursor = this.getActiveSelectable(); + $selectables = this._getSelectables(); + oldIndex = $oldCursor ? $selectables.index($oldCursor) : -1; + newIndex = oldIndex + delta; + newIndex = (newIndex + 1) % ($selectables.length + 1) - 1; + newIndex = newIndex < -1 ? $selectables.length - 1 : newIndex; + return newIndex === -1 ? null : $selectables.eq(newIndex); + }, + setCursor: function setCursor($selectable) { + this._removeCursor(); + if ($selectable = $selectable && $selectable.first()) { + $selectable.addClass(this.classes.cursor); + this._ensureVisible($selectable); + } + }, + getSelectableData: function getSelectableData($el) { + return $el && $el.length ? Dataset.extractData($el) : null; + }, + getActiveSelectable: function getActiveSelectable() { + var $selectable = this._getSelectables().filter(this.selectors.cursor).first(); + return $selectable.length ? $selectable : null; + }, + getTopSelectable: function getTopSelectable() { + var $selectable = this._getSelectables().first(); + return $selectable.length ? $selectable : null; + }, + update: function update(query) { + var isValidUpdate = query !== this.query; + if (isValidUpdate) { + this.query = query; + _.each(this.datasets, updateDataset); + } + return isValidUpdate; + function updateDataset(dataset) { + dataset.update(query); + } + }, + empty: function empty() { + _.each(this.datasets, clearDataset); + this.query = null; + this.$node.addClass(this.classes.empty); + function clearDataset(dataset) { + dataset.clear(); + } + }, + destroy: function destroy() { + this.$node.off(".tt"); + this.$node = $("
"); + _.each(this.datasets, destroyDataset); + function destroyDataset(dataset) { + dataset.destroy(); + } + } + }); + return Menu; + }(); + var DefaultMenu = function() { + "use strict"; + var s = Menu.prototype; + function DefaultMenu() { + Menu.apply(this, [].slice.call(arguments, 0)); + } + _.mixin(DefaultMenu.prototype, Menu.prototype, { + open: function open() { + !this._allDatasetsEmpty() && this._show(); + return s.open.apply(this, [].slice.call(arguments, 0)); + }, + close: function close() { + this._hide(); + return s.close.apply(this, [].slice.call(arguments, 0)); + }, + _onRendered: function onRendered() { + if (this._allDatasetsEmpty()) { + this._hide(); + } else { + this.isOpen() && this._show(); + } + return s._onRendered.apply(this, [].slice.call(arguments, 0)); + }, + _onCleared: function onCleared() { + if (this._allDatasetsEmpty()) { + this._hide(); + } else { + this.isOpen() && this._show(); + } + return s._onCleared.apply(this, [].slice.call(arguments, 0)); + }, + setLanguageDirection: function setLanguageDirection(dir) { + this.$node.css(dir === "ltr" ? this.css.ltr : this.css.rtl); + return s.setLanguageDirection.apply(this, [].slice.call(arguments, 0)); + }, + _hide: function hide() { + this.$node.hide(); + }, + _show: function show() { + this.$node.css("display", "block"); + } + }); + return DefaultMenu; + }(); + var Typeahead = function() { + "use strict"; + function Typeahead(o, www) { + var onFocused, onBlurred, onEnterKeyed, onTabKeyed, onEscKeyed, onUpKeyed, onDownKeyed, onLeftKeyed, onRightKeyed, onQueryChanged, onWhitespaceChanged; + o = o || {}; + if (!o.input) { + $.error("missing input"); + } + if (!o.menu) { + $.error("missing menu"); + } + if (!o.eventBus) { + $.error("missing event bus"); + } + www.mixin(this); + this.eventBus = o.eventBus; + this.minLength = _.isNumber(o.minLength) ? o.minLength : 1; + this.input = o.input; + this.menu = o.menu; + this.enabled = true; + this.active = false; + this.input.hasFocus() && this.activate(); + this.dir = this.input.getLangDir(); + this._hacks(); + this.menu.bind().onSync("selectableClicked", this._onSelectableClicked, this).onSync("asyncRequested", this._onAsyncRequested, this).onSync("asyncCanceled", this._onAsyncCanceled, this).onSync("asyncReceived", this._onAsyncReceived, this).onSync("datasetRendered", this._onDatasetRendered, this).onSync("datasetCleared", this._onDatasetCleared, this); + onFocused = c(this, "activate", "open", "_onFocused"); + onBlurred = c(this, "deactivate", "_onBlurred"); + onEnterKeyed = c(this, "isActive", "isOpen", "_onEnterKeyed"); + onTabKeyed = c(this, "isActive", "isOpen", "_onTabKeyed"); + onEscKeyed = c(this, "isActive", "_onEscKeyed"); + onUpKeyed = c(this, "isActive", "open", "_onUpKeyed"); + onDownKeyed = c(this, "isActive", "open", "_onDownKeyed"); + onLeftKeyed = c(this, "isActive", "isOpen", "_onLeftKeyed"); + onRightKeyed = c(this, "isActive", "isOpen", "_onRightKeyed"); + onQueryChanged = c(this, "_openIfActive", "_onQueryChanged"); + onWhitespaceChanged = c(this, "_openIfActive", "_onWhitespaceChanged"); + this.input.bind().onSync("focused", onFocused, this).onSync("blurred", onBlurred, this).onSync("enterKeyed", onEnterKeyed, this).onSync("tabKeyed", onTabKeyed, this).onSync("escKeyed", onEscKeyed, this).onSync("upKeyed", onUpKeyed, this).onSync("downKeyed", onDownKeyed, this).onSync("leftKeyed", onLeftKeyed, this).onSync("rightKeyed", onRightKeyed, this).onSync("queryChanged", onQueryChanged, this).onSync("whitespaceChanged", onWhitespaceChanged, this).onSync("langDirChanged", this._onLangDirChanged, this); + } + _.mixin(Typeahead.prototype, { + _hacks: function hacks() { + var $input, $menu; + $input = this.input.$input || $("
"); + $menu = this.menu.$node || $("
"); + $input.on("blur.tt", function($e) { + var active, isActive, hasActive; + active = document.activeElement; + isActive = $menu.is(active); + hasActive = $menu.has(active).length > 0; + if (_.isMsie() && (isActive || hasActive)) { + $e.preventDefault(); + $e.stopImmediatePropagation(); + _.defer(function() { + $input.focus(); + }); + } + }); + $menu.on("mousedown.tt", function($e) { + $e.preventDefault(); + }); + }, + _onSelectableClicked: function onSelectableClicked(type, $el) { + this.select($el); + }, + _onDatasetCleared: function onDatasetCleared() { + this._updateHint(); + }, + _onDatasetRendered: function onDatasetRendered(type, dataset, suggestions, async) { + this._updateHint(); + this.eventBus.trigger("render", suggestions, async, dataset); + }, + _onAsyncRequested: function onAsyncRequested(type, dataset, query) { + this.eventBus.trigger("asyncrequest", query, dataset); + }, + _onAsyncCanceled: function onAsyncCanceled(type, dataset, query) { + this.eventBus.trigger("asynccancel", query, dataset); + }, + _onAsyncReceived: function onAsyncReceived(type, dataset, query) { + this.eventBus.trigger("asyncreceive", query, dataset); + }, + _onFocused: function onFocused() { + this._minLengthMet() && this.menu.update(this.input.getQuery()); + }, + _onBlurred: function onBlurred() { + if (this.input.hasQueryChangedSinceLastFocus()) { + this.eventBus.trigger("change", this.input.getQuery()); + } + }, + _onEnterKeyed: function onEnterKeyed(type, $e) { + var $selectable; + if ($selectable = this.menu.getActiveSelectable()) { + this.select($selectable) && $e.preventDefault(); + } + }, + _onTabKeyed: function onTabKeyed(type, $e) { + var $selectable; + if ($selectable = this.menu.getActiveSelectable()) { + this.select($selectable) && $e.preventDefault(); + } else if ($selectable = this.menu.getTopSelectable()) { + this.autocomplete($selectable) && $e.preventDefault(); + } + }, + _onEscKeyed: function onEscKeyed() { + this.close(); + }, + _onUpKeyed: function onUpKeyed() { + this.moveCursor(-1); + }, + _onDownKeyed: function onDownKeyed() { + this.moveCursor(+1); + }, + _onLeftKeyed: function onLeftKeyed() { + if (this.dir === "rtl" && this.input.isCursorAtEnd()) { + this.autocomplete(this.menu.getTopSelectable()); + } + }, + _onRightKeyed: function onRightKeyed() { + if (this.dir === "ltr" && this.input.isCursorAtEnd()) { + this.autocomplete(this.menu.getTopSelectable()); + } + }, + _onQueryChanged: function onQueryChanged(e, query) { + this._minLengthMet(query) ? this.menu.update(query) : this.menu.empty(); + }, + _onWhitespaceChanged: function onWhitespaceChanged() { + this._updateHint(); + }, + _onLangDirChanged: function onLangDirChanged(e, dir) { + if (this.dir !== dir) { + this.dir = dir; + this.menu.setLanguageDirection(dir); + } + }, + _openIfActive: function openIfActive() { + this.isActive() && this.open(); + }, + _minLengthMet: function minLengthMet(query) { + query = _.isString(query) ? query : this.input.getQuery() || ""; + return query.length >= this.minLength; + }, + _updateHint: function updateHint() { + var $selectable, data, val, query, escapedQuery, frontMatchRegEx, match; + $selectable = this.menu.getTopSelectable(); + data = this.menu.getSelectableData($selectable); + val = this.input.getInputValue(); + if (data && !_.isBlankString(val) && !this.input.hasOverflow()) { + query = Input.normalizeQuery(val); + escapedQuery = _.escapeRegExChars(query); + frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i"); + match = frontMatchRegEx.exec(data.val); + match && this.input.setHint(val + match[1]); + } else { + this.input.clearHint(); + } + }, + isEnabled: function isEnabled() { + return this.enabled; + }, + enable: function enable() { + this.enabled = true; + }, + disable: function disable() { + this.enabled = false; + }, + isActive: function isActive() { + return this.active; + }, + activate: function activate() { + if (this.isActive()) { + return true; + } else if (!this.isEnabled() || this.eventBus.before("active")) { + return false; + } else { + this.active = true; + this.eventBus.trigger("active"); + return true; + } + }, + deactivate: function deactivate() { + if (!this.isActive()) { + return true; + } else if (this.eventBus.before("idle")) { + return false; + } else { + this.active = false; + this.close(); + this.eventBus.trigger("idle"); + return true; + } + }, + isOpen: function isOpen() { + return this.menu.isOpen(); + }, + open: function open() { + if (!this.isOpen() && !this.eventBus.before("open")) { + this.menu.open(); + this._updateHint(); + this.eventBus.trigger("open"); + } + return this.isOpen(); + }, + close: function close() { + if (this.isOpen() && !this.eventBus.before("close")) { + this.menu.close(); + this.input.clearHint(); + this.input.resetInputValue(); + this.eventBus.trigger("close"); + } + return !this.isOpen(); + }, + setVal: function setVal(val) { + this.input.setQuery(_.toStr(val)); + }, + getVal: function getVal() { + return this.input.getQuery(); + }, + select: function select($selectable) { + var data = this.menu.getSelectableData($selectable); + if (data && !this.eventBus.before("select", data.obj)) { + this.input.setQuery(data.val, true); + this.eventBus.trigger("select", data.obj); + this.close(); + return true; + } + return false; + }, + autocomplete: function autocomplete($selectable) { + var query, data, isValid; + query = this.input.getQuery(); + data = this.menu.getSelectableData($selectable); + isValid = data && query !== data.val; + if (isValid && !this.eventBus.before("autocomplete", data.obj)) { + this.input.setQuery(data.val); + this.eventBus.trigger("autocomplete", data.obj); + return true; + } + return false; + }, + moveCursor: function moveCursor(delta) { + var query, $candidate, data, payload, cancelMove; + query = this.input.getQuery(); + $candidate = this.menu.selectableRelativeToCursor(delta); + data = this.menu.getSelectableData($candidate); + payload = data ? data.obj : null; + cancelMove = this._minLengthMet() && this.menu.update(query); + if (!cancelMove && !this.eventBus.before("cursorchange", payload)) { + this.menu.setCursor($candidate); + if (data) { + this.input.setInputValue(data.val); + } else { + this.input.resetInputValue(); + this._updateHint(); + } + this.eventBus.trigger("cursorchange", payload); + return true; + } + return false; + }, + destroy: function destroy() { + this.input.destroy(); + this.menu.destroy(); + } + }); + return Typeahead; + function c(ctx) { + var methods = [].slice.call(arguments, 1); + return function() { + var args = [].slice.call(arguments); + _.each(methods, function(method) { + return ctx[method].apply(ctx, args); + }); + }; + } + }(); + (function() { + "use strict"; + var old, keys, methods; + old = $.fn.typeahead; + keys = { + www: "tt-www", + attrs: "tt-attrs", + typeahead: "tt-typeahead" + }; + methods = { + initialize: function initialize(o, datasets) { + var www; + datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1); + o = o || {}; + www = WWW(o.classNames); + return this.each(attach); + function attach() { + var $input, $wrapper, $hint, $menu, defaultHint, defaultMenu, eventBus, input, menu, typeahead, MenuConstructor; + _.each(datasets, function(d) { + d.highlight = !!o.highlight; + }); + $input = $(this); + $wrapper = $(www.html.wrapper); + $hint = $elOrNull(o.hint); + $menu = $elOrNull(o.menu); + defaultHint = o.hint !== false && !$hint; + defaultMenu = o.menu !== false && !$menu; + defaultHint && ($hint = buildHintFromInput($input, www)); + defaultMenu && ($menu = $(www.html.menu).css(www.css.menu)); + $hint && $hint.val(""); + $input = prepInput($input, www); + if (defaultHint || defaultMenu) { + $wrapper.css(www.css.wrapper); + $input.css(defaultHint ? www.css.input : www.css.inputWithNoHint); + $input.wrap($wrapper).parent().prepend(defaultHint ? $hint : null).append(defaultMenu ? $menu : null); + } + MenuConstructor = defaultMenu ? DefaultMenu : Menu; + eventBus = new EventBus({ + el: $input + }); + input = new Input({ + hint: $hint, + input: $input + }, www); + menu = new MenuConstructor({ + node: $menu, + datasets: datasets + }, www); + typeahead = new Typeahead({ + input: input, + menu: menu, + eventBus: eventBus, + minLength: o.minLength + }, www); + $input.data(keys.www, www); + $input.data(keys.typeahead, typeahead); + } + }, + isEnabled: function isEnabled() { + var enabled; + ttEach(this.first(), function(t) { + enabled = t.isEnabled(); + }); + return enabled; + }, + enable: function enable() { + ttEach(this, function(t) { + t.enable(); + }); + return this; + }, + disable: function disable() { + ttEach(this, function(t) { + t.disable(); + }); + return this; + }, + isActive: function isActive() { + var active; + ttEach(this.first(), function(t) { + active = t.isActive(); + }); + return active; + }, + activate: function activate() { + ttEach(this, function(t) { + t.activate(); + }); + return this; + }, + deactivate: function deactivate() { + ttEach(this, function(t) { + t.deactivate(); + }); + return this; + }, + isOpen: function isOpen() { + var open; + ttEach(this.first(), function(t) { + open = t.isOpen(); + }); + return open; + }, + open: function open() { + ttEach(this, function(t) { + t.open(); + }); + return this; + }, + close: function close() { + ttEach(this, function(t) { + t.close(); + }); + return this; + }, + select: function select(el) { + var success = false, $el = $(el); + ttEach(this.first(), function(t) { + success = t.select($el); + }); + return success; + }, + autocomplete: function autocomplete(el) { + var success = false, $el = $(el); + ttEach(this.first(), function(t) { + success = t.autocomplete($el); + }); + return success; + }, + moveCursor: function moveCursoe(delta) { + var success = false; + ttEach(this.first(), function(t) { + success = t.moveCursor(delta); + }); + return success; + }, + val: function val(newVal) { + var query; + if (!arguments.length) { + ttEach(this.first(), function(t) { + query = t.getVal(); + }); + return query; + } else { + ttEach(this, function(t) { + t.setVal(newVal); + }); + return this; + } + }, + destroy: function destroy() { + ttEach(this, function(typeahead, $input) { + revert($input); + typeahead.destroy(); + }); + return this; + } + }; + $.fn.typeahead = function(method) { + if (methods[method]) { + return methods[method].apply(this, [].slice.call(arguments, 1)); + } else { + return methods.initialize.apply(this, arguments); + } + }; + $.fn.typeahead.noConflict = function noConflict() { + $.fn.typeahead = old; + return this; + }; + function ttEach($els, fn) { + $els.each(function() { + var $input = $(this), typeahead; + (typeahead = $input.data(keys.typeahead)) && fn(typeahead, $input); + }); + } + function buildHintFromInput($input, www) { + return $input.clone().addClass(www.classes.hint).removeData().css(www.css.hint).css(getBackgroundStyles($input)).prop("readonly", true).removeAttr("id name placeholder required").attr({ + autocomplete: "off", + spellcheck: "false", + tabindex: -1 + }); + } + function prepInput($input, www) { + $input.data(keys.attrs, { + dir: $input.attr("dir"), + autocomplete: $input.attr("autocomplete"), + spellcheck: $input.attr("spellcheck"), + style: $input.attr("style") + }); + $input.addClass(www.classes.input).attr({ + autocomplete: "off", + spellcheck: false + }); + try { + !$input.attr("dir") && $input.attr("dir", "auto"); + } catch (e) {} + return $input; + } + function getBackgroundStyles($el) { + return { + backgroundAttachment: $el.css("background-attachment"), + backgroundClip: $el.css("background-clip"), + backgroundColor: $el.css("background-color"), + backgroundImage: $el.css("background-image"), + backgroundOrigin: $el.css("background-origin"), + backgroundPosition: $el.css("background-position"), + backgroundRepeat: $el.css("background-repeat"), + backgroundSize: $el.css("background-size") + }; + } + function revert($input) { + var www, $wrapper; + www = $input.data(keys.www); + $wrapper = $input.parent().filter(www.selectors.wrapper); + _.each($input.data(keys.attrs), function(val, key) { + _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); + }); + $input.removeData(keys.typeahead).removeData(keys.www).removeData(keys.attr).removeClass(www.classes.input); + if ($wrapper.length) { + $input.detach().insertAfter($wrapper); + $wrapper.remove(); + } + } + function $elOrNull(obj) { + var isValid, $el; + isValid = _.isJQuery(obj) || _.isElement(obj); + $el = isValid ? $(obj).first() : []; + return $el.length ? $el : null; + } + })(); +}); diff --git a/templates/base.html b/templates/base.html index 0bb98e65..bd1a4bb1 100644 --- a/templates/base.html +++ b/templates/base.html @@ -32,8 +32,11 @@ with this program; if not, write to the Free Software Foundation, Inc., {# Load CSS and JavaScript #} {% bootstrap_css %} + {% bootstrap_javascript %} + + {{ site_name }} : {% block title %}Accueil{% endblock %} From 111527b53baf2699ae57dc518ea69b37dc8fbdf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Thu, 5 Oct 2017 21:36:27 +0000 Subject: [PATCH 09/44] Gestion des hidden fields --- .../templatetags/bootstrap_form_typeahead.py | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py index 9c1363df..9b38b528 100644 --- a/machines/templatetags/bootstrap_form_typeahead.py +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -62,20 +62,23 @@ def bootstrap_form_typeahead(django_form, typeahead_fields, *args, **kwargs): t_fields = typeahead_fields.split(',') exclude = kwargs.get('exclude', None) exclude = exclude.split(',') if exclude else [] + hidden = [h.name for h in django_form.hidden_fields()] form = '' for f_name, f_value in django_form.fields.items() : if not f_name in exclude : if f_name in t_fields : - form += render_tag( - 'div', - attrs = {'class': 'form-group'}, - content = label_tag( f_name, f_value ) + - input_tag( f_name, f_value ) + - hidden_tag( f_name ) + - typeahead_full_script( f_name, f_value ) - ) - + if not f_name in hidden : + form += render_tag( + 'div', + attrs = {'class': 'form-group'}, + content = label_tag( f_name, f_value ) + + input_tag( f_name, f_value ) + + hidden_tag( f_name ) + + typeahead_full_script( f_name, f_value ) + ) + else: + form += hidden_tag( f_name ) else: form += render_field( f_value.get_bound_field(django_form, f_name), @@ -89,14 +92,15 @@ def bootstrap_form_typeahead(django_form, typeahead_fields, *args, **kwargs): def input_id( f_name ): return 'typeahead_input_'+f_name -def select_id( f_name ): +def hidden_id( f_name ): return 'typeahead_select_'+f_name def hidden_tag( f_name ): return render_tag( 'input', attrs={ - 'id': select_id(f_name), + 'id': hidden_id(f_name), + 'maxlength': 255, 'name': f_name, 'type': 'hidden', 'value': '' @@ -173,7 +177,7 @@ def typeahead_datasets( f_name ) : def typeahead_updater( f_name ): return 'function(evt, item) { ' \ - '$("#'+select_id(f_name)+'").val( item.key ); ' \ + '$("#'+hidden_id(f_name)+'").val( item.key ); ' \ 'return item; ' \ '}' From ba1f55adf75abe14d94d38d1a056501fdd797356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Thu, 5 Oct 2017 23:44:52 +0000 Subject: [PATCH 10/44] Utilise bootstrap_form_typeahead seulement sur les ip --- machines/templates/machines/machine.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machines/templates/machines/machine.html b/machines/templates/machines/machine.html index bc1ac30f..1b8646ea 100644 --- a/machines/templates/machines/machine.html +++ b/machines/templates/machines/machine.html @@ -45,7 +45,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% bootstrap_form machineform %} {% endif %} {% if interfaceform %} - {% bootstrap_form_typeahead interfaceform 'ipv4,type' %} + {% bootstrap_form_typeahead interfaceform 'ipv4' %} {% endif %} {% if domainform %} {% bootstrap_form domainform %} From 883258d0799504cb7a76bc002d36c1b509404e01 Mon Sep 17 00:00:00 2001 From: Pierre Cadart Date: Thu, 5 Oct 2017 23:47:01 +0000 Subject: [PATCH 11/44] Laisse bootstrap_form faire les rendu au maximum --- .../templatetags/bootstrap_form_typeahead.py | 49 +++++++------------ 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py index 9b38b528..528bd767 100644 --- a/machines/templatetags/bootstrap_form_typeahead.py +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -20,6 +20,7 @@ from django import template from django.utils.safestring import mark_safe +from django.forms import TextInput from bootstrap3.templatetags.bootstrap3 import bootstrap_form from bootstrap3.utils import render_tag from bootstrap3.forms import render_field @@ -67,18 +68,24 @@ def bootstrap_form_typeahead(django_form, typeahead_fields, *args, **kwargs): form = '' for f_name, f_value in django_form.fields.items() : if not f_name in exclude : - if f_name in t_fields : - if not f_name in hidden : + if f_name in t_fields and not f_name in hidden : + f_bound = f_value.get_bound_field( django_form, f_name ) + f_value.widget = TextInput( + attrs={ + 'name': 'typeahead_'+f_name, + } + ) + form += render_field( + f_value.get_bound_field( django_form, f_name ), + *args, + **kwargs + ) form += render_tag( 'div', attrs = {'class': 'form-group'}, - content = label_tag( f_name, f_value ) + - input_tag( f_name, f_value ) + - hidden_tag( f_name ) + + content = hidden_tag( f_bound, f_name ) + typeahead_full_script( f_name, f_value ) ) - else: - form += hidden_tag( f_name ) else: form += render_field( f_value.get_bound_field(django_form, f_name), @@ -89,45 +96,23 @@ def bootstrap_form_typeahead(django_form, typeahead_fields, *args, **kwargs): return mark_safe( form ) -def input_id( f_name ): - return 'typeahead_input_'+f_name +def input_id( f_name ) : + return 'id_'+f_name def hidden_id( f_name ): - return 'typeahead_select_'+f_name + return 'typeahead_hidden_'+f_name def hidden_tag( f_name ): return render_tag( 'input', attrs={ 'id': hidden_id(f_name), - 'maxlength': 255, 'name': f_name, 'type': 'hidden', 'value': '' } ) -def label_tag( f_name, f_value ): - return render_tag( - 'label', - attrs={ - 'class': 'control-label', - 'for': input_id(f_name) - }, - content=f_value.label - ) - -def input_tag( f_name, f_value ): - return render_tag( - 'input', - attrs={ - 'class': 'form-control', - 'id': input_id(f_name), - 'type': 'text', - 'placeholder': f_value.empty_label - }, - ) - def typeahead_full_script( f_name, f_value ) : js_content = \ '$("#'+input_id(f_name)+'").ready( function() {\n' + \ From df360ddea79631183864433804ff26d5ff147018 Mon Sep 17 00:00:00 2001 From: Pierre Cadart Date: Thu, 5 Oct 2017 23:47:33 +0000 Subject: [PATCH 12/44] =?UTF-8?q?Permet=20de=20reprendre=20la=20m=C3=AAme?= =?UTF-8?q?=20ip=20quand=20on=20=C3=A9dite=20une=20interface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- machines/forms.py | 4 ++++ machines/templatetags/bootstrap_form_typeahead.py | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/machines/forms.py b/machines/forms.py index 44e80c25..ca167638 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -60,6 +60,8 @@ class EditInterfaceForm(ModelForm): if "ipv4" in self.fields: self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4" self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True) + # Add it's own address + self.fields['ipv4'].queryset |= IpList.objects.filter(id=self.fields['ipv4'].get_bound_field(self, 'ipv4').value()) if "machine" in self.fields: self.fields['machine'].queryset = Machine.objects.all().select_related('user') @@ -92,6 +94,8 @@ class BaseEditInterfaceForm(EditInterfaceForm): if not infra: self.fields['type'].queryset = MachineType.objects.filter(ip_type__in=IpType.objects.filter(need_infra=False)) self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)) + # Add it's own address + self.fields['ipv4'].queryset |= IpList.objects.filter(id=self.fields['ipv4'].get_bound_field(self, 'ipv4').value()) else: self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True) diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py index 528bd767..60a75484 100644 --- a/machines/templatetags/bootstrap_form_typeahead.py +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -102,20 +102,21 @@ def input_id( f_name ) : def hidden_id( f_name ): return 'typeahead_hidden_'+f_name -def hidden_tag( f_name ): +def hidden_tag( f_bound, f_name ): return render_tag( 'input', attrs={ 'id': hidden_id(f_name), 'name': f_name, 'type': 'hidden', - 'value': '' + 'value': f_bound.value() } ) def typeahead_full_script( f_name, f_value ) : js_content = \ '$("#'+input_id(f_name)+'").ready( function() {\n' + \ + reset_input( f_name, f_value ) + '\n' + \ typeahead_choices( f_value ) + '\n' + \ typeahead_engine () + '\n' + \ '$("#'+input_id(f_name) + '").typeahead(\n' + \ @@ -128,6 +129,9 @@ def typeahead_full_script( f_name, f_value ) : return render_tag( 'script', content=mark_safe( js_content ) ) +def reset_input( f_name, f_value ) : + return '$("#'+input_id(f_name)+'").val("'+f_value.empty_label+'");' + def typeahead_choices( f_value ) : return 'var choices = [' + \ ', '.join([ \ From 18c27f836924864ae8757adb2489efeba643edb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Fri, 6 Oct 2017 00:27:34 +0000 Subject: [PATCH 13/44] =?UTF-8?q?Ajoute=20des=20suggestions=20quand=20rien?= =?UTF-8?q?=20n'est=20=C3=A9crit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templatetags/bootstrap_form_typeahead.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py index 60a75484..de7869d8 100644 --- a/machines/templatetags/bootstrap_form_typeahead.py +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -142,7 +142,7 @@ def typeahead_choices( f_value ) : '];' def typeahead_engine () : - return 'var choices = new Bloodhound({ ' \ + return 'var engine = new Bloodhound({ ' \ 'datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"), ' \ 'queryTokenizer: Bloodhound.tokenizers.whitespace, ' \ 'local: choices, ' \ @@ -153,7 +153,7 @@ def typeahead_datasets( f_name ) : return '{ ' \ 'hint: true, ' \ 'highlight: true, ' \ - 'minLength: 1 ' \ + 'minLength: 0 ' \ '}, ' \ '{ ' \ 'templates: { ' \ @@ -161,7 +161,18 @@ def typeahead_datasets( f_name ) : '}, ' \ 'display: "value", ' \ 'name: "'+f_name+'", ' \ - 'source: choices ' \ + 'source: function(q, sync) {' \ + 'if (q === "") {' \ + 'var nb = 10;' \ + 'var first = [] ;' \ + 'for ( var i = 0 ; i < nb ; i++ ) {' \ + 'first.push(choices[i].value);' \ + '}' \ + 'sync(engine.get(first));' \ + '} else {' \ + 'engine.search(q, sync)' \ + '}' \ + '}' \ '}' def typeahead_updater( f_name ): From 24a0f35b45a7413e91aaed33e99341f04073fd2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Fri, 6 Oct 2017 00:50:13 +0000 Subject: [PATCH 14/44] Empty label dans le placeholder + bind (''->empty value) --- machines/templatetags/bootstrap_form_typeahead.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py index de7869d8..ac22185e 100644 --- a/machines/templatetags/bootstrap_form_typeahead.py +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -73,6 +73,7 @@ def bootstrap_form_typeahead(django_form, typeahead_fields, *args, **kwargs): f_value.widget = TextInput( attrs={ 'name': 'typeahead_'+f_name, + 'placeholder': f_value.empty_label } ) form += render_field( @@ -124,13 +125,16 @@ def typeahead_full_script( f_name, f_value ) : ').bind(\n' + \ '"typeahead:select", ' + \ typeahead_updater( f_name ) + '\n' + \ + ').bind(\n' + \ + '"typeahead:change", ' + \ + typeahead_change( f_name ) + '\n' + \ ')\n' + \ '});\n' return render_tag( 'script', content=mark_safe( js_content ) ) def reset_input( f_name, f_value ) : - return '$("#'+input_id(f_name)+'").val("'+f_value.empty_label+'");' + return '$("#'+input_id(f_name)+'").val("");' def typeahead_choices( f_value ) : return 'var choices = [' + \ @@ -179,5 +183,11 @@ def typeahead_updater( f_name ): return 'function(evt, item) { ' \ '$("#'+hidden_id(f_name)+'").val( item.key ); ' \ 'return item; ' \ - '}' + '}' +def typeahead_change( f_name ): + return 'function(evt) { ' \ + 'if (evt.currentTarget.value === "") {' \ + '$("#'+hidden_id(f_name)+'").val(""); ' \ + '}' \ + '}' From 804e1116d0e7124b36b823ffe9f92792dca546d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Fri, 6 Oct 2017 00:58:46 +0000 Subject: [PATCH 15/44] =?UTF-8?q?Fix=20:=20Il=20y=20a=20pas=20forc=C3=A9me?= =?UTF-8?q?nt=20nb=20suggestions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- machines/templatetags/bootstrap_form_typeahead.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py index ac22185e..32eb3818 100644 --- a/machines/templatetags/bootstrap_form_typeahead.py +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -169,7 +169,7 @@ def typeahead_datasets( f_name ) : 'if (q === "") {' \ 'var nb = 10;' \ 'var first = [] ;' \ - 'for ( var i = 0 ; i < nb ; i++ ) {' \ + 'for ( var i=0 ; i Date: Fri, 6 Oct 2017 04:17:45 +0200 Subject: [PATCH 16/44] More docstrings sur topologie --- topologie/models.py | 61 +++++++++++++++++++++++++++++++++++++-------- topologie/views.py | 15 +++++++++++ 2 files changed, 65 insertions(+), 11 deletions(-) diff --git a/topologie/models.py b/topologie/models.py index 7ac10243..c02c0c51 100644 --- a/topologie/models.py +++ b/topologie/models.py @@ -34,17 +34,11 @@ import reversion from machines.models import Vlan -def make_port_related(port): - related_port = port.related - related_port.related = port - related_port.save() - -def clean_port_related(port): - related_port = port.related_port - related_port.related = None - related_port.save() class Stack(models.Model): + """ Un objet stack. Regrouppe des switchs en foreign key + , contient une id de stack, un switch id min et max dans + le stack""" PRETTY_NAME = "Stack de switchs" name = models.CharField(max_length=32, blank=True, null=True) @@ -57,15 +51,25 @@ class Stack(models.Model): return " ".join([self.name, self.stack_id]) def save(self, *args, **kwargs): + self.clean() if not self.name: self.name = self.stack_id super(Stack, self).save(*args, **kwargs) def clean(self): + """ Verification que l'id_max < id_min""" if self.member_id_max < self.member_id_min: raise ValidationError({'member_id_max':"L'id maximale est inférieure à l'id minimale"}) class Switch(models.Model): + """ Definition d'un switch. Contient un nombre de ports (number), + un emplacement (location), un stack parent (optionnel, stack) + et un id de membre dans le stack (stack_member_id) + relié en onetoone à une interface + Pourquoi ne pas avoir fait hériter switch de interface ? + Principalement par méconnaissance de la puissance de cette façon de faire. + Ceci étant entendu, django crée en interne un onetoone, ce qui a un + effet identique avec ce que l'on fait ici""" PRETTY_NAME = "Switch / Commutateur" switch_interface = models.OneToOneField('machines.Interface', on_delete=models.CASCADE) @@ -82,6 +86,7 @@ class Switch(models.Model): return str(self.location) + ' ' + str(self.switch_interface) def clean(self): + """ Verifie que l'id stack est dans le bon range""" if self.stack is not None: if self.stack_member_id is not None: if (self.stack_member_id > self.stack.member_id_max) or (self.stack_member_id < self.stack.member_id_min): @@ -90,6 +95,20 @@ class Switch(models.Model): raise ValidationError({'stack_member_id': "L'id dans la stack ne peut être nul"}) class Port(models.Model): + """ Definition d'un port. Relié à un switch(foreign_key), + un port peut etre relié de manière exclusive à : + - une chambre (room) + - une machine (serveur etc) (machine_interface) + - un autre port (uplink) (related) + Champs supplémentaires : + - RADIUS (mode STRICT : connexion sur port uniquement si machine + d'un adhérent à jour de cotisation et que la chambre est également à jour de cotisation + mode COMMON : vérification uniquement du statut de la machine + mode NO : accepte toute demande venant du port et place sur le vlan normal + mode BLOQ : rejet de toute authentification + - vlan_force : override la politique générale de placement vlan, permet + de forcer un port sur un vlan particulier. S'additionne à la politique + RADIUS""" PRETTY_NAME = "Port de switch" STATES = ( ('NO', 'NO'), @@ -110,7 +129,26 @@ class Port(models.Model): class Meta: unique_together = ('switch', 'port') + def make_port_related(self): + """ Synchronise le port distant sur self""" + related_port = self.related + related_port.related = self + related_port.save() + + def clean_port_related(self): + """ Supprime la relation related sur self""" + related_port = self.related_port + related_port.related = None + related_port.save() + def clean(self): + """ Verifie que un seul de chambre, interface_parent et related_port est rempli. + Verifie que le related n'est pas le port lui-même.... + Verifie que le related n'est pas déjà occupé par une machine ou une chambre. Si + ce n'est pas le cas, applique la relation related + Si un port related point vers self, on nettoie la relation + A priori pas d'autre solution que de faire ça à la main. A priori tout cela est dans + un bloc transaction, donc pas de problème de cohérence""" if hasattr(self, 'switch'): if self.port > self.switch.number: raise ValidationError("Ce port ne peut exister, numero trop élevé") @@ -122,14 +160,15 @@ class Port(models.Model): if self.related.machine_interface or self.related.room: raise ValidationError("Le port relié est déjà occupé, veuillez le libérer avant de créer une relation") else: - make_port_related(self) + self.make_port_related() elif hasattr(self, 'related_port'): - clean_port_related(self) + self.clean_port_related() def __str__(self): return str(self.switch) + " - " + str(self.port) class Room(models.Model): + """ Une chambre/local contenant une prise murale""" PRETTY_NAME = "Chambre/ Prise murale" name = models.CharField(max_length=255, unique=True) diff --git a/topologie/views.py b/topologie/views.py index 4e453d7c..eba00e0c 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -44,12 +44,14 @@ from preferences.models import AssoOption, GeneralOption @login_required @permission_required('cableur') def index(request): + """ Vue d'affichage de tous les swicthes""" switch_list = Switch.objects.order_by('stack','stack_member_id','location').select_related('switch_interface__domain__extension').select_related('switch_interface__ipv4').select_related('switch_interface__domain') return render(request, 'topologie/index.html', {'switch_list': switch_list}) @login_required @permission_required('cableur') def history(request, object, id): + """ Vue générique pour afficher l'historique complet d'un objet""" if object == 'switch': try: object_instance = Switch.objects.get(pk=id) @@ -95,6 +97,7 @@ def history(request, object, id): @login_required @permission_required('cableur') def index_port(request, switch_id): + """ Affichage de l'ensemble des ports reliés à un switch particulier""" try: switch = Switch.objects.get(pk=switch_id) except Switch.DoesNotExist: @@ -106,6 +109,7 @@ def index_port(request, switch_id): @login_required @permission_required('cableur') def index_room(request): + """ Affichage de l'ensemble des chambres""" room_list = Room.objects.order_by('name') options, created = GeneralOption.objects.get_or_create() pagination_number = options.pagination_number @@ -131,6 +135,7 @@ def index_stack(request): @login_required @permission_required('infra') def new_port(request, switch_id): + """ Nouveau port""" try: switch = Switch.objects.get(pk=switch_id) except Switch.DoesNotExist: @@ -154,6 +159,7 @@ def new_port(request, switch_id): @login_required @permission_required('infra') def edit_port(request, port_id): + """ Edition d'un port. Permet de changer le switch parent et l'affectation du port""" try: port_object = Port.objects.select_related('switch__switch_interface__domain__extension').select_related('machine_interface__domain__extension').select_related('machine_interface__switch').select_related('room').select_related('related').get(pk=port_id) except Port.DoesNotExist: @@ -172,6 +178,7 @@ def edit_port(request, port_id): @login_required @permission_required('infra') def del_port(request,port_id): + """ Supprime le port""" try: port = Port.objects.get(pk=port_id) except Port.DoesNotExist: @@ -263,6 +270,9 @@ def edit_switchs_stack(request,stack_id): @login_required @permission_required('infra') def new_switch(request): + """ Creation d'un switch. Cree en meme temps l'interface et la machine associée. + Vue complexe. Appelle successivement les 4 models forms adaptés : machine, + interface, domain et switch""" switch = NewSwitchForm(request.POST or None) machine = NewMachineForm(request.POST or None) interface = AddInterfaceForm(request.POST or None, infra=request.user.has_perms(('infra',))) @@ -304,6 +314,8 @@ def new_switch(request): @login_required @permission_required('infra') def edit_switch(request, switch_id): + """ Edition d'un switch. Permet de chambre nombre de ports, place dans le stack, + interface et machine associée""" try: switch = Switch.objects.get(pk=switch_id) except Switch.DoesNotExist: @@ -341,6 +353,7 @@ def edit_switch(request, switch_id): @login_required @permission_required('infra') def new_room(request): + """Nouvelle chambre """ room = EditRoomForm(request.POST or None) if room.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -354,6 +367,7 @@ def new_room(request): @login_required @permission_required('infra') def edit_room(request, room_id): + """ Edition numero et details de la chambre""" try: room = Room.objects.get(pk=room_id) except Room.DoesNotExist: @@ -372,6 +386,7 @@ def edit_room(request, room_id): @login_required @permission_required('infra') def del_room(request, room_id): + """ Suppression d'un chambre""" try: room = Room.objects.get(pk=room_id) except Room.DoesNotExist: From b5df315be9e0ae1c7522f9de1e3f211b51ce93c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Fri, 6 Oct 2017 11:32:49 +0000 Subject: [PATCH 17/44] Notifie l'objet quand on change manuellement sa valeur --- machines/templatetags/bootstrap_form_typeahead.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py index 32eb3818..e4d8ace7 100644 --- a/machines/templatetags/bootstrap_form_typeahead.py +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -182,6 +182,7 @@ def typeahead_datasets( f_name ) : def typeahead_updater( f_name ): return 'function(evt, item) { ' \ '$("#'+hidden_id(f_name)+'").val( item.key ); ' \ + '$("#'+hidden_id(f_name)+'").change();' \ 'return item; ' \ '}' @@ -189,5 +190,6 @@ def typeahead_change( f_name ): return 'function(evt) { ' \ 'if (evt.currentTarget.value === "") {' \ '$("#'+hidden_id(f_name)+'").val(""); ' \ + '$("#'+hidden_id(f_name)+'").change();' \ '}' \ '}' From 876f98841270efaf2700f44626924cdb341832e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Fri, 6 Oct 2017 23:12:46 +0000 Subject: [PATCH 18/44] Tag bootstrap_for_typeahead customisable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Laisse la possibilité de changer certaines parties du script JS avec une string pour plus de facilité. Les parties modifiables sont le tableau des choix et le filtre qui match les query. --- .../templatetags/bootstrap_form_typeahead.py | 101 ++++++++++++------ 1 file changed, 70 insertions(+), 31 deletions(-) diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py index e4d8ace7..9253c995 100644 --- a/machines/templatetags/bootstrap_form_typeahead.py +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -41,7 +41,7 @@ def bootstrap_form_typeahead(django_form, typeahead_fields, *args, **kwargs): bootstrap_form_typeahead **Parameters**: - + form The form that is to be rendered @@ -49,6 +49,29 @@ def bootstrap_form_typeahead(django_form, typeahead_fields, *args, **kwargs): A list of field names (comma separated) that should be rendered with typeahead instead of the default bootstrap renderer. + choices + A string representing the choices in JS. The choices must be an + array of objects. Each of those objects must at least have the + fields 'key' (value to send) and 'value' (value to display). + Other fields can be added as desired. + If not specified, the key is the id of the object and the value + is its string representation as in a normal bootstrap form. + Example : + choices='[{key:0,value:"choice0",extrafield:"data0"}, {...},...];' + + match_func + A string representing a valid JS function used in the dataset to + overload the matching engine. This function is used the source of + the dataset. This function receives 2 parameters, the query and + the synchronize function as specified in typeahead.js documentation. + If needed, the local variables 'choices' and 'engine' contains + respectively the array of possible values and the engine to match + queries with possible values. + If not specified, the function used display up to the 10 first + elements if the query is empty and else the matching results. + Example : + match_func='function(q, sync) { engine.search(q, sync); }' + See boostrap_form_ for other arguments **Usage**:: @@ -56,15 +79,18 @@ def bootstrap_form_typeahead(django_form, typeahead_fields, *args, **kwargs): {% bootstrap_form_typeahead form ['field1[,field2[,...]]] %} **Example**: - - {% bootstrap_form_typeahead form 'ipv4' %} + + {% bootstrap_form_typeahead form 'ipv4' choices='[...]' %} """ + t_fields = typeahead_fields.split(',') exclude = kwargs.get('exclude', None) exclude = exclude.split(',') if exclude else [] + t_choices = kwargs.get('choices', {}) + t_match_func = kwargs.get('match_func', {}) hidden = [h.name for h in django_form.hidden_fields()] - + form = '' for f_name, f_value in django_form.fields.items() : if not f_name in exclude : @@ -85,7 +111,12 @@ def bootstrap_form_typeahead(django_form, typeahead_fields, *args, **kwargs): 'div', attrs = {'class': 'form-group'}, content = hidden_tag( f_bound, f_name ) + - typeahead_full_script( f_name, f_value ) + typeahead_js( + f_name, + f_value, + t_choices, + t_match_func + ) ) else: form += render_field( @@ -94,7 +125,6 @@ def bootstrap_form_typeahead(django_form, typeahead_fields, *args, **kwargs): **kwargs ) - return mark_safe( form ) def input_id( f_name ) : @@ -114,14 +144,22 @@ def hidden_tag( f_bound, f_name ): } ) -def typeahead_full_script( f_name, f_value ) : +def typeahead_js( f_name, f_value, t_choices, t_match_func ) : + + choices = mark_safe(t_choices[f_name]) if f_name in t_choices.keys() \ + else default_choices( f_value ) + + match_func = mark_safe(t_match_func[f_name]) \ + if f_name in t_match_func.keys() \ + else default_match_func() + js_content = \ '$("#'+input_id(f_name)+'").ready( function() {\n' + \ reset_input( f_name, f_value ) + '\n' + \ - typeahead_choices( f_value ) + '\n' + \ - typeahead_engine () + '\n' + \ + 'var choices = ' + choices + '\n' + \ + 'var engine = ' + default_engine() + '\n' + \ '$("#'+input_id(f_name) + '").typeahead(\n' + \ - typeahead_datasets( f_name ) + \ + default_datasets( f_name, match_func ) + \ ').bind(\n' + \ '"typeahead:select", ' + \ typeahead_updater( f_name ) + '\n' + \ @@ -136,8 +174,8 @@ def typeahead_full_script( f_name, f_value ) : def reset_input( f_name, f_value ) : return '$("#'+input_id(f_name)+'").val("");' -def typeahead_choices( f_value ) : - return 'var choices = [' + \ +def default_choices( f_value ) : + return '[' + \ ', '.join([ \ '{key: ' + (str(choice[0]) if choice[0] != '' else '""') + \ ', value: "' + str(choice[1]) + '"}' \ @@ -145,40 +183,41 @@ def typeahead_choices( f_value ) : ]) + \ '];' -def typeahead_engine () : - return 'var engine = new Bloodhound({ ' \ +def default_engine () : + return 'new Bloodhound({ ' \ 'datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"), ' \ 'queryTokenizer: Bloodhound.tokenizers.whitespace, ' \ 'local: choices, ' \ - 'identify: function(obj) { return obj.value; } ' \ + 'identify: function(obj) { return obj.key; } ' \ '});' -def typeahead_datasets( f_name ) : +def default_datasets( f_name, match_func ) : return '{ ' \ 'hint: true, ' \ 'highlight: true, ' \ 'minLength: 0 ' \ '}, ' \ '{ ' \ - 'templates: { ' \ - 'suggestion: Handlebars.compile("
{{value}}
") ' \ - '}, ' \ 'display: "value", ' \ 'name: "'+f_name+'", ' \ - 'source: function(q, sync) {' \ - 'if (q === "") {' \ - 'var nb = 10;' \ - 'var first = [] ;' \ - 'for ( var i=0 ; i Date: Fri, 6 Oct 2017 23:16:19 +0000 Subject: [PATCH 19/44] =?UTF-8?q?Affiche=20uniquement=20les=20ips=20associ?= =?UTF-8?q?=C3=A9es=20au=20machine=20type=20choisi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Utilise la customisation du tag bootstrap_form_typeahead pour ajouter un champs correspondant au type de machine dans les données et filtrer les match pour ne garder que les résultat qui ont le bon champs 'type' --- machines/forms.py | 6 +++--- machines/views.py | 41 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 4 deletions(-) diff --git a/machines/forms.py b/machines/forms.py index ca167638..0f6554cf 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -25,7 +25,7 @@ from __future__ import unicode_literals from django.forms import ModelForm, Form, ValidationError from django import forms from .models import Domain, Machine, Interface, IpList, MachineType, Extension, Mx, Text, Ns, Service, Vlan, Nas, IpType -from django.db.models import Q +from django.db.models import Q, F from django.core.validators import validate_email from users.models import User @@ -75,9 +75,9 @@ class AddInterfaceForm(EditInterfaceForm): self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4" if not infra: self.fields['type'].queryset = MachineType.objects.filter(ip_type__in=IpType.objects.filter(need_infra=False)) - self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)) + self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)).annotate(type=F('ip_type__machinetype__id')) else: - self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True) + self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).annotate(type=F('ip_type__machinetype__id')) class NewInterfaceForm(EditInterfaceForm): class Meta(EditInterfaceForm.Meta): diff --git a/machines/views.py b/machines/views.py index 4268519b..f70a95e4 100644 --- a/machines/views.py +++ b/machines/views.py @@ -74,6 +74,43 @@ def form(ctx, template, request): c.update(csrf(request)) return render(request, template, c) +def generate_ipv4_choices( field ) : + return '[{key: "", value: "' + str(field.empty_label) + '", type: -1},' + \ + ', '.join([ \ + '{key: ' + str(ip.id) + ',' \ + ' value: "' + str(ip.ipv4) + '",' \ + ' type: ' + str(ip.type) + '}' \ + for ip in field.queryset \ + ]) + \ + '];' + +def generate_ipv4_match_func() : + return 'function(q, sync) {' \ + 'var select = function (array, nb, filter) {' \ + 'var i=0; var res=[];' \ + 'while (nb >= 0 && i < array.length) {' \ + 'if (filter(array[i])) {' \ + 'res.push(array[i]);' \ + 'nb -= 1;' \ + '}' \ + 'i += 1;' \ + '}' \ + 'return res;' \ + '};' \ + 'var filter = function (elt) {' \ + 'return elt.type == -1 || elt.type == $("#id_type").val();' \ + '};' \ + 'var cb = function (a) { sync(a.filter(filter)); };' \ + 'if (q === "") {' \ + 'sync( engine.get( select(choices, 10, filter).map(' \ + 'function (elt) { return elt.key; }' \ + ') ) );' \ + '} else {' \ + 'engine.search(q, cb);' \ + '}' \ + '}' + + @login_required def new_machine(request, userid): try: @@ -117,7 +154,9 @@ def new_machine(request, userid): reversion.set_comment("Création") messages.success(request, "La machine a été créée") return redirect("/users/profil/" + str(user.id)) - return form({'machineform': machine, 'interfaceform': interface, 'domainform': domain}, 'machines/machine.html', request) + i_choices = { 'ipv4': generate_ipv4_choices( interface.fields['ipv4'] ) } + i_match_func = { 'ipv4': generate_ipv4_match_func() } + return form({'machineform': machine, 'interfaceform': interface, 'domainform': domain, 'i_choices': i_choices, 'i_match_func': i_match_func}, 'machines/machine.html', request) @login_required def edit_interface(request, interfaceid): From cc33995ced30682f784577a3227e4f91d4371104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Fri, 6 Oct 2017 23:19:26 +0000 Subject: [PATCH 20/44] =?UTF-8?q?Fix=20:=20Ne=20pas=20casser=20le=20formul?= =?UTF-8?q?aire=20si=20des=20champs=20ne=20sont=20pas=20sp=C3=A9cifi=C3=A9?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vérifie la présence des champs de customistion du tag et agit en conséquence. --- machines/templates/machines/machine.html | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/machines/templates/machines/machine.html b/machines/templates/machines/machine.html index 1b8646ea..5e90447e 100644 --- a/machines/templates/machines/machine.html +++ b/machines/templates/machines/machine.html @@ -45,7 +45,19 @@ with this program; if not, write to the Free Software Foundation, Inc., {% bootstrap_form machineform %} {% endif %} {% if interfaceform %} + {% if i_choices %} + {% if i_match_func %} + {% bootstrap_form_typeahead interfaceform 'ipv4' choices=i_choices match_func=i_match_func %} + {% else %} + {% bootstrap_form_typeahead interfaceform 'ipv4' choices=i_choices %} + {% endif %} + {% else %} + {% if i_match_func %} + {% bootstrap_form_typeahead interfaceform 'ipv4' match_func=i_match_func %} + {% else %} {% bootstrap_form_typeahead interfaceform 'ipv4' %} + {% endif %} + {% endif %} {% endif %} {% if domainform %} {% bootstrap_form domainform %} From b0d9c2843d92f936d43036551afc7d9961de433a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Fri, 6 Oct 2017 23:37:22 +0000 Subject: [PATCH 21/44] =?UTF-8?q?Ajout=20d'en-t=C3=AAtes=20coding:=20utf-8?= =?UTF-8?q?=20et=20copyrights?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- machines/forms.py | 2 ++ machines/serializers.py | 1 + machines/templates/machines/machine.html | 1 + machines/templatetags/__init__.py | 1 + machines/templatetags/bootstrap_form_typeahead.py | 1 + machines/tests.py | 1 + machines/urls.py | 1 + machines/views.py | 2 ++ 8 files changed, 10 insertions(+) diff --git a/machines/forms.py b/machines/forms.py index 0f6554cf..434e7970 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -1,3 +1,4 @@ +# -*- mode: python; coding: utf-8 -*- # Re2o est un logiciel d'administration développé initiallement au rezometz. Il # se veut agnostique au réseau considéré, de manière à être installable en # quelques clics. @@ -5,6 +6,7 @@ # Copyright © 2017 Gabriel Détraz # Copyright © 2017 Goulven Kermarec # Copyright © 2017 Augustin Lemesle +# Copyright © 2017 Maël Kervella # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/machines/serializers.py b/machines/serializers.py index bfe5f295..fe59c4de 100644 --- a/machines/serializers.py +++ b/machines/serializers.py @@ -1,3 +1,4 @@ +# -*- mode: python; coding: utf-8 -*- # Re2o est un logiciel d'administration développé initiallement au rezometz. Il # se veut agnostique au réseau considéré, de manière à être installable en # quelques clics. diff --git a/machines/templates/machines/machine.html b/machines/templates/machines/machine.html index 5e90447e..f325791d 100644 --- a/machines/templates/machines/machine.html +++ b/machines/templates/machines/machine.html @@ -7,6 +7,7 @@ quelques clics. Copyright © 2017 Gabriel Détraz Copyright © 2017 Goulven Kermarec Copyright © 2017 Augustin Lemesle +Copyright © 2017 Maël Kervella This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/machines/templatetags/__init__.py b/machines/templatetags/__init__.py index 5e24b69e..d9561b4b 100644 --- a/machines/templatetags/__init__.py +++ b/machines/templatetags/__init__.py @@ -1,3 +1,4 @@ +# -*- mode: python; coding: utf-8 -*- # Re2o est un logiciel d'administration développé initiallement au rezometz. Il # se veut agnostique au réseau considéré, de manière à être installable en # quelques clics. diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py index 9253c995..239eed35 100644 --- a/machines/templatetags/bootstrap_form_typeahead.py +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -1,3 +1,4 @@ +# -*- mode: python; coding: utf-8 -*- # Re2o est un logiciel d'administration développé initiallement au rezometz. Il # se veut agnostique au réseau considéré, de manière à être installable en # quelques clics. diff --git a/machines/tests.py b/machines/tests.py index 21fa6d24..dedc0021 100644 --- a/machines/tests.py +++ b/machines/tests.py @@ -1,3 +1,4 @@ +# -*- mode: python; coding: utf-8 -*- # Re2o est un logiciel d'administration développé initiallement au rezometz. Il # se veut agnostique au réseau considéré, de manière à être installable en # quelques clics. diff --git a/machines/urls.py b/machines/urls.py index cdf3d5fb..d8164b6f 100644 --- a/machines/urls.py +++ b/machines/urls.py @@ -1,3 +1,4 @@ +# -*- mode: python; coding: utf-8 -*- # Re2o est un logiciel d'administration développé initiallement au rezometz. Il # se veut agnostique au réseau considéré, de manière à être installable en # quelques clics. diff --git a/machines/views.py b/machines/views.py index f70a95e4..34ebd14a 100644 --- a/machines/views.py +++ b/machines/views.py @@ -1,3 +1,4 @@ +# -*- mode: python; coding: utf-8 -*- # Re2o est un logiciel d'administration développé initiallement au rezometz. Il # se veut agnostique au réseau considéré, de manière à être installable en # quelques clics. @@ -5,6 +6,7 @@ # Copyright © 2017 Gabriel Détraz # Copyright © 2017 Goulven Kermarec # Copyright © 2017 Augustin Lemesle +# Copyright © 2017 Maël Kervella # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by From fa1dbe97199b3a574964cc000a53591ac9b3598c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 7 Oct 2017 00:35:48 +0000 Subject: [PATCH 22/44] Utilise typeahead dynamique sur tous les forms avec ip --- machines/forms.py | 10 +++++----- machines/views.py | 8 ++++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/machines/forms.py b/machines/forms.py index 434e7970..5c8aef37 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -61,9 +61,9 @@ class EditInterfaceForm(ModelForm): self.fields['type'].empty_label = "Séléctionner un type de machine" if "ipv4" in self.fields: self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4" - self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True) + self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).annotate(type=F('ip_type__machinetype__id')) # Add it's own address - self.fields['ipv4'].queryset |= IpList.objects.filter(id=self.fields['ipv4'].get_bound_field(self, 'ipv4').value()) + self.fields['ipv4'].queryset |= IpList.objects.filter(id=self.fields['ipv4'].get_bound_field(self, 'ipv4').value()).annotate(type=F('ip_type__machinetype__id')) if "machine" in self.fields: self.fields['machine'].queryset = Machine.objects.all().select_related('user') @@ -95,11 +95,11 @@ class BaseEditInterfaceForm(EditInterfaceForm): self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4" if not infra: self.fields['type'].queryset = MachineType.objects.filter(ip_type__in=IpType.objects.filter(need_infra=False)) - self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)) + self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)).annotate(type=F('ip_type__machinetype__id')) # Add it's own address - self.fields['ipv4'].queryset |= IpList.objects.filter(id=self.fields['ipv4'].get_bound_field(self, 'ipv4').value()) + self.fields['ipv4'].queryset |= IpList.objects.filter(id=self.fields['ipv4'].get_bound_field(self, 'ipv4').value()).annotate(type=F('ip_type__machinetype__id')) else: - self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True) + self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).annotate(type=F('ip_type__machinetype__id')) class AliasForm(ModelForm): class Meta: diff --git a/machines/views.py b/machines/views.py index 34ebd14a..49e9d1c4 100644 --- a/machines/views.py +++ b/machines/views.py @@ -195,7 +195,9 @@ def edit_interface(request, interfaceid): reversion.set_comment("Champs modifié(s) : %s" % ', '.join(field for field in domain_form.changed_data)) messages.success(request, "La machine a été modifiée") return redirect("/users/profil/" + str(interface.machine.user.id)) - return form({'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form}, 'machines/machine.html', request) + i_choices = { 'ipv4': generate_ipv4_choices( interface_form.fields['ipv4'] ) } + i_match_func = { 'ipv4': generate_ipv4_match_func() } + return form({'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form, 'i_choices': i_choices, 'i_match_func': i_match_func}, 'machines/machine.html', request) @login_required def del_machine(request, machineid): @@ -251,7 +253,9 @@ def new_interface(request, machineid): reversion.set_comment("Création") messages.success(request, "L'interface a été ajoutée") return redirect("/users/profil/" + str(machine.user.id)) - return form({'interfaceform': interface_form, 'domainform': domain_form}, 'machines/machine.html', request) + i_choices = { 'ipv4': generate_ipv4_choices( interface_form.fields['ipv4'] ) } + i_match_func = { 'ipv4': generate_ipv4_match_func() } + return form({'interfaceform': interface_form, 'domainform': domain_form, 'i_choices': i_choices, 'i_match_func': i_match_func}, 'machines/machine.html', request) @login_required def del_interface(request, interfaceid): From d4e012b9e3fe2009a5f6b3053e4f5b3e7371af51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 7 Oct 2017 00:36:29 +0000 Subject: [PATCH 23/44] =?UTF-8?q?R=C3=A9ordonne=20les=20fields=20pour=20pl?= =?UTF-8?q?us=20de=20coh=C3=A9rence?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- machines/forms.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/machines/forms.py b/machines/forms.py index 5c8aef37..c25ed1f3 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -52,7 +52,7 @@ class BaseEditMachineForm(EditMachineForm): class EditInterfaceForm(ModelForm): class Meta: model = Interface - fields = '__all__' + fields = ['machine', 'type', 'ipv4', 'mac_address', 'details'] def __init__(self, *args, **kwargs): super(EditInterfaceForm, self).__init__(*args, **kwargs) @@ -83,11 +83,11 @@ class AddInterfaceForm(EditInterfaceForm): class NewInterfaceForm(EditInterfaceForm): class Meta(EditInterfaceForm.Meta): - fields = ['mac_address','type','details'] + fields = ['type','mac_address','details'] class BaseEditInterfaceForm(EditInterfaceForm): class Meta(EditInterfaceForm.Meta): - fields = ['ipv4','mac_address','type','details'] + fields = ['type','ipv4','mac_address','details'] def __init__(self, *args, **kwargs): infra = kwargs.pop('infra') From 9f5022bf801ee8625ccd3eca0774147e19b701ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 7 Oct 2017 00:36:55 +0000 Subject: [PATCH 24/44] =?UTF-8?q?Ajoute=20des=20titres=20dans=20le=20form?= =?UTF-8?q?=20pour=20plus=20de=20claret=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- machines/templates/machines/machine.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/machines/templates/machines/machine.html b/machines/templates/machines/machine.html index f325791d..6982ae1a 100644 --- a/machines/templates/machines/machine.html +++ b/machines/templates/machines/machine.html @@ -43,9 +43,11 @@ with this program; if not, write to the Free Software Foundation, Inc., {% csrf_token %} {% if machineform %} +

Machine

{% bootstrap_form machineform %} {% endif %} {% if interfaceform %} +

Interface

{% if i_choices %} {% if i_match_func %} {% bootstrap_form_typeahead interfaceform 'ipv4' choices=i_choices match_func=i_match_func %} @@ -61,6 +63,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endif %} {% endif %} {% if domainform %} +

Domaine

{% bootstrap_form domainform %} {% endif %} {% bootstrap_button "Créer ou modifier" button_type="submit" icon="star" %} From b8288bfc5736c1252682884a1738314b9185ba78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 7 Oct 2017 00:42:54 +0000 Subject: [PATCH 25/44] Fix : Quand on retire un elt d'une liste, la taille de la liste diminue Faisait crasher dans certains cas --- logs/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/logs/views.py b/logs/views.py index 17b92947..d84a2f43 100644 --- a/logs/views.py +++ b/logs/views.py @@ -106,7 +106,7 @@ def index(request): 'user_id': v.revision.user_id, 'version': v } else : - to_remove.append(i) + to_remove.insert(0,i) # Remove all tagged invalid items for i in to_remove : versions.object_list.pop(i) From 1c9a2fe4ac7faf6a58aac513ffa8987143cea243 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 7 Oct 2017 16:38:02 +0000 Subject: [PATCH 26/44] Tout le script JS est dans une fonction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pour povoir être relaod plusieurs fois --- .../templatetags/bootstrap_form_typeahead.py | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py index 239eed35..d1b3978e 100644 --- a/machines/templatetags/bootstrap_form_typeahead.py +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -155,20 +155,23 @@ def typeahead_js( f_name, f_value, t_choices, t_match_func ) : else default_match_func() js_content = \ - '$("#'+input_id(f_name)+'").ready( function() {\n' + \ - reset_input( f_name, f_value ) + '\n' + \ - 'var choices = ' + choices + '\n' + \ - 'var engine = ' + default_engine() + '\n' + \ + 'var choices = ' + choices + ';\n' + \ + 'var setup = function() {\n' + \ + 'var engine = ' + deafult_engine() + ';\n' + \ + '$("#'+input_id(f_name) + '").typeahead("destroy");\n' + \ '$("#'+input_id(f_name) + '").typeahead(\n' + \ - default_datasets( f_name, match_func ) + \ - ').bind(\n' + \ - '"typeahead:select", ' + \ - typeahead_updater( f_name ) + '\n' + \ - ').bind(\n' + \ - '"typeahead:change", ' + \ - typeahead_change( f_name ) + '\n' + \ - ')\n' + \ - '});\n' + default_datasets( f_name, match_func ) + '\n' + \ + ');\n' + \ + reset_input( f_name, f_value ) + '\n' + \ + '};\n' + \ + '$("#'+input_id(f_name) + '").bind(\n' + \ + '"typeahead:select", ' + \ + typeahead_updater( f_name ) + '\n' + \ + ').bind(\n' + \ + '"typeahead:change", ' + \ + typeahead_change( f_name ) + '\n' + \ + ');\n' + js_content += '$("#'+input_id(f_name)+'").ready( setup );\n' return render_tag( 'script', content=mark_safe( js_content ) ) @@ -182,7 +185,7 @@ def default_choices( f_value ) : ', value: "' + str(choice[1]) + '"}' \ for choice in f_value.choices \ ]) + \ - '];' + ']' def default_engine () : return 'new Bloodhound({ ' \ @@ -217,7 +220,6 @@ def default_match_func () : 'engine.search(q, sync);' \ '}' \ '}' -# matches.filter(function (elt) {return elt.type == $("#id_type").val();});sync(matches);}) def typeahead_updater( f_name ): return 'function(evt, item) { ' \ @@ -230,6 +232,6 @@ def typeahead_change( f_name ): return 'function(evt) { ' \ 'if (evt.currentTarget.value === "") {' \ '$("#'+hidden_id(f_name)+'").val(""); ' \ - '$("#'+hidden_id(f_name)+'").change();' \ + '$("#'+hidden_id(f_name)+'").change();' \ '}' \ '}' From 957dc567e2b66e7f5836af2b65337fa1cdc6817c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 7 Oct 2017 16:47:13 +0000 Subject: [PATCH 27/44] =?UTF-8?q?Renomme=20des=20variables=20pour=20=C3=A9?= =?UTF-8?q?viter=20les=20conflits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templatetags/bootstrap_form_typeahead.py | 32 +++++++++---------- machines/views.py | 4 +-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py index d1b3978e..fb95bb23 100644 --- a/machines/templatetags/bootstrap_form_typeahead.py +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -151,13 +151,13 @@ def typeahead_js( f_name, f_value, t_choices, t_match_func ) : else default_choices( f_value ) match_func = mark_safe(t_match_func[f_name]) \ - if f_name in t_match_func.keys() \ - else default_match_func() + if f_name in t_match_func.keys() \ + else default_match_func( f_name ) js_content = \ - 'var choices = ' + choices + ';\n' + \ - 'var setup = function() {\n' + \ - 'var engine = ' + deafult_engine() + ';\n' + \ + 'var choices_'+f_name+' = ' + choices + ';\n' + \ + 'var setup_'+f_name+' = function() {\n' + \ + 'var engine_'+f_name+' = ' + default_engine() + ';\n' + \ '$("#'+input_id(f_name) + '").typeahead("destroy");\n' + \ '$("#'+input_id(f_name) + '").typeahead(\n' + \ default_datasets( f_name, match_func ) + '\n' + \ @@ -171,7 +171,7 @@ def typeahead_js( f_name, f_value, t_choices, t_match_func ) : '"typeahead:change", ' + \ typeahead_change( f_name ) + '\n' + \ ');\n' - js_content += '$("#'+input_id(f_name)+'").ready( setup );\n' + js_content += '$("#'+input_id(f_name)+'").ready( setup_'+f_name+' );\n' return render_tag( 'script', content=mark_safe( js_content ) ) @@ -187,13 +187,13 @@ def default_choices( f_value ) : ]) + \ ']' -def default_engine () : - return 'new Bloodhound({ ' \ +def default_engine ( f_name ) : + return 'new Bloodhound({ ' \ 'datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"), ' \ 'queryTokenizer: Bloodhound.tokenizers.whitespace, ' \ - 'local: choices, ' \ - 'identify: function(obj) { return obj.key; } ' \ - '});' + 'local: choices_'+f_name+', ' \ + 'identify: function(obj) { return obj.key; } ' \ + '})' def default_datasets( f_name, match_func ) : return '{ ' \ @@ -207,17 +207,17 @@ def default_datasets( f_name, match_func ) : 'source: '+match_func + \ '}' -def default_match_func () : +def default_match_func ( f_name ) : return 'function(q, sync) {' \ 'if (q === "") {' \ 'var nb = 10;' \ 'var first = [] ;' \ - 'for ( var i=0 ; i Date: Sat, 7 Oct 2017 16:48:19 +0000 Subject: [PATCH 28/44] Fix: Reset aussi la value dans le hidden quand on reset le tt_input --- machines/templatetags/bootstrap_form_typeahead.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py index fb95bb23..1d6770f6 100644 --- a/machines/templatetags/bootstrap_form_typeahead.py +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -176,7 +176,8 @@ def typeahead_js( f_name, f_value, t_choices, t_match_func ) : return render_tag( 'script', content=mark_safe( js_content ) ) def reset_input( f_name, f_value ) : - return '$("#'+input_id(f_name)+'").val("");' + return '$("#'+input_id(f_name)+'").typeahead("val","");\n' \ + '$("#'+hidden_id(f_name)+'").val("");' def default_choices( f_value ) : return '[' + \ From 1c98b68d377f4e915c280ce4f87e54e2bc4e88e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 7 Oct 2017 16:51:21 +0000 Subject: [PATCH 29/44] =?UTF-8?q?Fix:=20R=C3=A9cup=C3=A8re=20une=20valeur?= =?UTF-8?q?=20correcte=20quand=20la=20valeur=20du=20tt=5Finput=20a=20chang?= =?UTF-8?q?=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- machines/templatetags/bootstrap_form_typeahead.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py index 1d6770f6..8a929fbf 100644 --- a/machines/templatetags/bootstrap_form_typeahead.py +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -231,7 +231,7 @@ def typeahead_updater( f_name ): def typeahead_change( f_name ): return 'function(evt) { ' \ - 'if (evt.currentTarget.value === "") {' \ + 'if ($("#'+input_id(f_name)+'").typeahead("val") === "") {' \ '$("#'+hidden_id(f_name)+'").val(""); ' \ '$("#'+hidden_id(f_name)+'").change();' \ '}' \ From b697b4e53b5370c85b81de31da4b941281743e7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 7 Oct 2017 16:58:09 +0000 Subject: [PATCH 30/44] =?UTF-8?q?Ajoute=20des=20param=C3=A8tres=20dans=20l?= =?UTF-8?q?e=20bft=20tag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les paramètres concernant bft sont maintenant spécifiés via un dictionnaire pour alleger un peu le code. Ajout des paramètres customisant l'engine et la possibilité de reload quand un autre elt a changé --- machines/templates/machines/machine.html | 16 +++--------- .../templatetags/bootstrap_form_typeahead.py | 26 ++++++++++++++----- machines/views.py | 6 ++--- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/machines/templates/machines/machine.html b/machines/templates/machines/machine.html index 6982ae1a..4b308a11 100644 --- a/machines/templates/machines/machine.html +++ b/machines/templates/machines/machine.html @@ -48,19 +48,11 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endif %} {% if interfaceform %}

Interface

- {% if i_choices %} - {% if i_match_func %} - {% bootstrap_form_typeahead interfaceform 'ipv4' choices=i_choices match_func=i_match_func %} - {% else %} - {% bootstrap_form_typeahead interfaceform 'ipv4' choices=i_choices %} - {% endif %} - {% else %} - {% if i_match_func %} - {% bootstrap_form_typeahead interfaceform 'ipv4' match_func=i_match_func %} - {% else %} + {% if i_bft_param %} + {% bootstrap_form_typeahead interfaceform 'ipv4' bft_param=i_bft_param %} + {% else %} {% bootstrap_form_typeahead interfaceform 'ipv4' %} - {% endif %} - {% endif %} + {% endif %} {% endif %} {% if domainform %}

Domaine

diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py index 8a929fbf..45680fa1 100644 --- a/machines/templatetags/bootstrap_form_typeahead.py +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -84,12 +84,14 @@ def bootstrap_form_typeahead(django_form, typeahead_fields, *args, **kwargs): {% bootstrap_form_typeahead form 'ipv4' choices='[...]' %} """ - t_fields = typeahead_fields.split(',') - exclude = kwargs.get('exclude', None) + params = kwargs.get('bft_param', {}) + exclude = params.get('exclude', None) exclude = exclude.split(',') if exclude else [] - t_choices = kwargs.get('choices', {}) - t_match_func = kwargs.get('match_func', {}) + t_choices = params.get('choices', {}) + t_engine = params.get('engine', {}) + t_match_func = params.get('match_func', {}) + t_update_on = params.get('update_on', {}) hidden = [h.name for h in django_form.hidden_fields()] form = '' @@ -116,7 +118,9 @@ def bootstrap_form_typeahead(django_form, typeahead_fields, *args, **kwargs): f_name, f_value, t_choices, - t_match_func + t_engine, + t_match_func, + t_update_on ) ) else: @@ -145,19 +149,25 @@ def hidden_tag( f_bound, f_name ): } ) -def typeahead_js( f_name, f_value, t_choices, t_match_func ) : +def typeahead_js( f_name, f_value, + t_choices, t_engine, t_match_func, t_update_on ) : choices = mark_safe(t_choices[f_name]) if f_name in t_choices.keys() \ else default_choices( f_value ) + engine = mark_safe(t_engine[f_name]) if f_name in t_engine.keys() \ + else default_engine ( f_name ) + match_func = mark_safe(t_match_func[f_name]) \ if f_name in t_match_func.keys() \ else default_match_func( f_name ) + update_on = t_update_on[f_name] if f_name in t_update_on.keys() else [] + js_content = \ 'var choices_'+f_name+' = ' + choices + ';\n' + \ 'var setup_'+f_name+' = function() {\n' + \ - 'var engine_'+f_name+' = ' + default_engine() + ';\n' + \ + 'var engine_'+f_name+' = ' + engine + ';\n' + \ '$("#'+input_id(f_name) + '").typeahead("destroy");\n' + \ '$("#'+input_id(f_name) + '").typeahead(\n' + \ default_datasets( f_name, match_func ) + '\n' + \ @@ -171,6 +181,8 @@ def typeahead_js( f_name, f_value, t_choices, t_match_func ) : '"typeahead:change", ' + \ typeahead_change( f_name ) + '\n' + \ ');\n' + for u_id in update_on : + js_content += '$("#'+u_id+'").change( setup_'+f_name+' );\n' js_content += '$("#'+input_id(f_name)+'").ready( setup_'+f_name+' );\n' return render_tag( 'script', content=mark_safe( js_content ) ) diff --git a/machines/views.py b/machines/views.py index 1aceda3f..ddc5b18f 100644 --- a/machines/views.py +++ b/machines/views.py @@ -159,7 +159,7 @@ def new_machine(request, userid): return redirect("/users/profil/" + str(user.id)) i_choices = { 'ipv4': generate_ipv4_choices( interface.fields['ipv4'] ) } i_match_func = { 'ipv4': generate_ipv4_match_func() } - return form({'machineform': machine, 'interfaceform': interface, 'domainform': domain, 'i_choices': i_choices, 'i_match_func': i_match_func}, 'machines/machine.html', request) + return form({'machineform': machine, 'interfaceform': interface, 'domainform': domain, 'i_bft_param': {'choices': i_choices, 'match_func': i_match_func}}, 'machines/machine.html', request) @login_required def edit_interface(request, interfaceid): @@ -198,7 +198,7 @@ def edit_interface(request, interfaceid): return redirect("/users/profil/" + str(interface.machine.user.id)) i_choices = { 'ipv4': generate_ipv4_choices( interface_form.fields['ipv4'] ) } i_match_func = { 'ipv4': generate_ipv4_match_func() } - return form({'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form, 'i_choices': i_choices, 'i_match_func': i_match_func}, 'machines/machine.html', request) + return form({'machineform': machine_form, 'interfaceform': interface_form, 'domainform': domain_form, 'i_bft_param': {'choices': i_choices, 'match_func': i_match_func}}, 'machines/machine.html', request) @login_required def del_machine(request, machineid): @@ -256,7 +256,7 @@ def new_interface(request, machineid): return redirect("/users/profil/" + str(machine.user.id)) i_choices = { 'ipv4': generate_ipv4_choices( interface_form.fields['ipv4'] ) } i_match_func = { 'ipv4': generate_ipv4_match_func() } - return form({'interfaceform': interface_form, 'domainform': domain_form, 'i_choices': i_choices, 'i_match_func': i_match_func}, 'machines/machine.html', request) + return form({'interfaceform': interface_form, 'domainform': domain_form, 'i_bft_param': { 'choices': i_choices, 'match_func': i_match_func }}, 'machines/machine.html', request) @login_required def del_interface(request, interfaceid): From bba28ee4d9d6cac42b8eaf5019080dffd957e303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 7 Oct 2017 17:00:55 +0000 Subject: [PATCH 31/44] =?UTF-8?q?Utilise=20les=20nouveaus=20param=20bft=20?= =?UTF-8?q?et=20change=20la=20structure=20de=20donn=C3=A9es?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Les nouveaux paramètres passés permettent de reload entièrement la source de données du typeahead quand le champ field est changé, ce qui permet au moteur de recherche de ne traiter que les données voulues et non de devoir filtrer ce qu'il faut afficher ou non parmis l'ensemble des ip possibles (tout type confondus). --- machines/forms.py | 15 ++++--- machines/views.py | 110 ++++++++++++++++++++++++++++++---------------- 2 files changed, 81 insertions(+), 44 deletions(-) diff --git a/machines/forms.py b/machines/forms.py index ca94414e..c9a3873f 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -63,9 +63,9 @@ class EditInterfaceForm(ModelForm): self.fields['type'].empty_label = "Séléctionner un type de machine" if "ipv4" in self.fields: self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4" - self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).annotate(type=F('ip_type__machinetype__id')) + self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).annotate(mtype_id=F('ip_type__machinetype__id')) # Add it's own address - self.fields['ipv4'].queryset |= IpList.objects.filter(id=self.fields['ipv4'].get_bound_field(self, 'ipv4').value()).annotate(type=F('ip_type__machinetype__id')) + self.fields['ipv4'].queryset |= IpList.objects.filter(id=self.fields['ipv4'].get_bound_field(self, 'ipv4').value()).annotate(mtype_id=F('ip_type__machinetype__id')) if "machine" in self.fields: self.fields['machine'].queryset = Machine.objects.all().select_related('user') @@ -79,9 +79,9 @@ class AddInterfaceForm(EditInterfaceForm): self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4" if not infra: self.fields['type'].queryset = MachineType.objects.filter(ip_type__in=IpType.objects.filter(need_infra=False)) - self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)).annotate(type=F('ip_type__machinetype__id')) + self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)).annotate(mtype_id=F('ip_type__machinetype__id')) else: - self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).annotate(type=F('ip_type__machinetype__id')) + self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).annotate(mtype_id=F('ip_type__machinetype__id')) class NewInterfaceForm(EditInterfaceForm): class Meta(EditInterfaceForm.Meta): @@ -97,11 +97,12 @@ class BaseEditInterfaceForm(EditInterfaceForm): self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4" if not infra: self.fields['type'].queryset = MachineType.objects.filter(ip_type__in=IpType.objects.filter(need_infra=False)) - self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)).annotate(type=F('ip_type__machinetype__id')) + self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)).annotate(mtype_id=F('ip_type__machinetype__id')) # Add it's own address - self.fields['ipv4'].queryset |= IpList.objects.filter(id=self.fields['ipv4'].get_bound_field(self, 'ipv4').value()).annotate(type=F('ip_type__machinetype__id')) + self.fields['ipv4'].queryset |= IpList.objects.filter(id=self.fields['ipv4'].get_bound_field(self, 'ipv4').value()).annotate(mtype_id=F('ip_type__machinetype__id')) else: - self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).annotate(type=F('ip_type__machinetype__id')) + self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).annotate(mtype_id=F('ip_type__machinetype__id')) + self.fields['ipv4'].queryset |= IpList.objects.filter(id=self.fields['ipv4'].get_bound_field(self, 'ipv4').value()).annotate(mtype_id=F('ip_type__machinetype__id')) class AliasForm(ModelForm): class Meta: diff --git a/machines/views.py b/machines/views.py index ddc5b18f..d9c59b15 100644 --- a/machines/views.py +++ b/machines/views.py @@ -55,6 +55,7 @@ from .models import IpType, Machine, Interface, IpList, MachineType, Extension, from users.models import User from users.models import all_has_access from preferences.models import GeneralOption, OptionalMachine +from .templatetags.bootstrap_form_typeahead import hidden_id, input_id def all_active_interfaces(): """Renvoie l'ensemble des machines autorisées à sortir sur internet """ @@ -77,42 +78,80 @@ def form(ctx, template, request): c.update(csrf(request)) return render(request, template, c) -def generate_ipv4_choices( field ) : - return '[{key: "", value: "' + str(field.empty_label) + '", type: -1},' + \ - ', '.join([ \ - '{key: ' + str(ip.id) + ',' \ - ' value: "' + str(ip.ipv4) + '",' \ - ' type: ' + str(ip.type) + '}' \ - for ip in field.queryset \ - ]) + \ - '];' +def f_type_id( is_type_tt ): + """ The id that will be used in HTML to store the value of the field + type. Depends on the fact that type is generate using typeahead or not + """ + return hidden_id('type') if is_type_tt else input_id('type') -def generate_ipv4_match_func() : +def generate_ipv4_choices( form ) : + """ Generate the parameter choices for the bootstrap_form_typeahead tag + """ + f_ipv4 = form.fields['ipv4'] + used_mtype_id = [] + choices = '{ "": [{key: "", value: "Choisissez d\'abord un type de machine"},' + mtype_id = -1 + + for ip in f_ipv4.queryset.order_by('mtype_id', 'id') : + if mtype_id != ip.mtype_id : + mtype_id = ip.mtype_id + used_mtype_id.append(mtype_id) + choices += '], "'+str(mtype_id)+'": [' + choices += '{key: "", value: "' + str(f_ipv4.empty_label) + '"},' + choices += '{key: ' + str(ip.id) + ', value: "' + str(ip.ipv4) + '"},' + + for t in form.fields['type'].queryset.exclude(id__in=used_mtype_id) : + choices += '], "'+str(t.id)+'": [' + choices += '{key: "", value: "' + str(f_ipv4.empty_label) + '"},' + choices += ']}' + return choices + +def generate_ipv4_engine( is_type_tt ) : + """ Generate the parameter engine for the bootstrap_form_typeahead tag + """ + return 'new Bloodhound({ ' \ + 'datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"), ' \ + 'queryTokenizer: Bloodhound.tokenizers.whitespace, ' \ + 'local: choices_ipv4[$("#'+f_type_id(is_type_tt)+'").val()], ' \ + 'identify: function(obj) { return obj.key; } ' \ + '})' + +def generate_ipv4_match_func( is_type_tt ) : + """ Generate the parameter match_func for the bootstrap_form_typeahead tag + """ return 'function(q, sync) {' \ - 'var select = function (array, nb, filter) {' \ - 'var i=0; var res=[];' \ - 'while (nb >= 0 && i < array.length) {' \ - 'if (filter(array[i])) {' \ - 'res.push(array[i]);' \ - 'nb -= 1;' \ - '}' \ - 'i += 1;' \ - '}' \ - 'return res;' \ - '};' \ - 'var filter = function (elt) {' \ - 'return elt.type == -1 || elt.type == $("#id_type").val();' \ - '};' \ - 'var cb = function (a) { sync(a.filter(filter)); };' \ 'if (q === "") {' \ - 'sync( engine.get( select(choices_ipv4, 10, filter).map(' \ - 'function (elt) { return elt.key; }' \ - ') ) );' \ + 'var nb = 10;' \ + 'var first = [] ;' \ + 'for(' \ + 'var i=0 ;' \ + 'i Date: Sat, 7 Oct 2017 17:04:23 +0000 Subject: [PATCH 32/44] Commentaires et doc + retire des espaces en trop en fin de ligne --- .../templatetags/bootstrap_form_typeahead.py | 121 ++++++++++++++---- machines/views.py | 10 +- 2 files changed, 104 insertions(+), 27 deletions(-) diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py index 45680fa1..f4cd357d 100644 --- a/machines/templatetags/bootstrap_form_typeahead.py +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -50,34 +50,97 @@ def bootstrap_form_typeahead(django_form, typeahead_fields, *args, **kwargs): A list of field names (comma separated) that should be rendered with typeahead instead of the default bootstrap renderer. - choices - A string representing the choices in JS. The choices must be an - array of objects. Each of those objects must at least have the - fields 'key' (value to send) and 'value' (value to display). - Other fields can be added as desired. - If not specified, the key is the id of the object and the value - is its string representation as in a normal bootstrap form. - Example : - choices='[{key:0,value:"choice0",extrafield:"data0"}, {...},...];' + bft_param + A dict of parameters for the bootstrap_form_typeahead tag. The + possible parameters are the following. - match_func - A string representing a valid JS function used in the dataset to - overload the matching engine. This function is used the source of - the dataset. This function receives 2 parameters, the query and - the synchronize function as specified in typeahead.js documentation. - If needed, the local variables 'choices' and 'engine' contains - respectively the array of possible values and the engine to match - queries with possible values. - If not specified, the function used display up to the 10 first - elements if the query is empty and else the matching results. - Example : - match_func='function(q, sync) { engine.search(q, sync); }' + choices + A dict of strings representing the choices in JS. The keys of + the dict are the names of the concerned fields. The choices + must be an array of objects. Each of those objects must at + least have the fields 'key' (value to send) and 'value' (value + to display). Other fields can be added as desired. + For a more complex structure you should also consider + reimplementing the engine and the match_func. + If not specified, the key is the id of the object and the value + is its string representation as in a normal bootstrap form. + Example : + 'choices' : { + 'field_A':'[{key:0,value:"choice0",extra:"data0"},{...},...]', + 'field_B':..., + ... + } + + engine + A dict of strings representating the engine used for matching + queries and possible values with typeahead. The keys of the + dict are the names of the concerned fields. The string is valid + JS code. + If not specified, BloodHound with relevant basic properties is + used. + Example : + 'engine' : {'field_A': 'new Bloodhound()', 'field_B': ..., ...} + + match_func + A dict of strings representing a valid JS function used in the + dataset to overload the matching engine. The keys of the dict + are the names of the concerned fields. This function is used + the source of the dataset. This function receives 2 parameters, + the query and the synchronize function as specified in + typeahead.js documentation. If needed, the local variables + 'choices_' and 'engine_' contains + respectively the array of all possible values and the engine + to match queries with possible values. + If not specified, the function used display up to the 10 first + elements if the query is empty and else the matching results. + Example : + 'match_func' : { + 'field_A': 'function(q, sync) { engine.search(q, sync); }', + 'field_B': ..., + ... + } + + update_on + A dict of list of ids that the values depends on. The engine + and the typeahead properties are recalculated and reapplied. + Example : + 'addition' : { + 'field_A' : [ 'id0', 'id1', ... ] , + 'field_B' : ... , + ... + } See boostrap_form_ for other arguments **Usage**:: - {% bootstrap_form_typeahead form ['field1[,field2[,...]]] %} + {% bootstrap_form_typeahead + form + [ '[,[,...]]' ] + [ { + [ 'choices': { + [ '': '' + [, '': '' + [, ... ] ] ] + } ] + [, 'engine': { + [ '': '' + [, '': '' + [, ... ] ] ] + } ] + [, 'match_func': { + [ '': '' + [, '': '' + [, ... ] ] ] + } ] + [, 'update_on': { + [ '': '' + [, '': '' + [, ... ] ] ] + } ] + } ] + [ ] + %} **Example**: @@ -133,12 +196,15 @@ def bootstrap_form_typeahead(django_form, typeahead_fields, *args, **kwargs): return mark_safe( form ) def input_id( f_name ) : + """ The id of the HTML input element """ return 'id_'+f_name def hidden_id( f_name ): + """ The id of the HTML hidden input element """ return 'typeahead_hidden_'+f_name def hidden_tag( f_bound, f_name ): + """ The HTML hidden input element """ return render_tag( 'input', attrs={ @@ -151,6 +217,7 @@ def hidden_tag( f_bound, f_name ): def typeahead_js( f_name, f_value, t_choices, t_engine, t_match_func, t_update_on ) : + """ The whole script to use """ choices = mark_safe(t_choices[f_name]) if f_name in t_choices.keys() \ else default_choices( f_value ) @@ -188,10 +255,12 @@ def typeahead_js( f_name, f_value, return render_tag( 'script', content=mark_safe( js_content ) ) def reset_input( f_name, f_value ) : + """ The JS script to reset the fields values """ return '$("#'+input_id(f_name)+'").typeahead("val","");\n' \ '$("#'+hidden_id(f_name)+'").val("");' def default_choices( f_value ) : + """ The JS script creating the variable choices_ """ return '[' + \ ', '.join([ \ '{key: ' + (str(choice[0]) if choice[0] != '' else '""') + \ @@ -201,6 +270,7 @@ def default_choices( f_value ) : ']' def default_engine ( f_name ) : + """ The JS script creating the variable engine_ """ return 'new Bloodhound({ ' \ 'datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"), ' \ 'queryTokenizer: Bloodhound.tokenizers.whitespace, ' \ @@ -209,6 +279,7 @@ def default_engine ( f_name ) : '})' def default_datasets( f_name, match_func ) : + """ The JS script creating the datasets to use with typeahead """ return '{ ' \ 'hint: true, ' \ 'highlight: true, ' \ @@ -221,6 +292,7 @@ def default_datasets( f_name, match_func ) : '}' def default_match_func ( f_name ) : + """ The JS script creating the matching function to use with typeahed """ return 'function(q, sync) {' \ 'if (q === "") {' \ 'var nb = 10;' \ @@ -235,6 +307,8 @@ def default_match_func ( f_name ) : '}' def typeahead_updater( f_name ): + """ The JS script creating the function triggered when an item is + selected through typeahead """ return 'function(evt, item) { ' \ '$("#'+hidden_id(f_name)+'").val( item.key ); ' \ '$("#'+hidden_id(f_name)+'").change();' \ @@ -242,6 +316,9 @@ def typeahead_updater( f_name ): '}' def typeahead_change( f_name ): + """ The JS script creating the function triggered when an item is changed + (i.e. looses focus and value has changed since the moment it gained focus + """ return 'function(evt) { ' \ 'if ($("#'+input_id(f_name)+'").typeahead("val") === "") {' \ '$("#'+hidden_id(f_name)+'").val(""); ' \ diff --git a/machines/views.py b/machines/views.py index d9c59b15..84b13d89 100644 --- a/machines/views.py +++ b/machines/views.py @@ -170,7 +170,7 @@ def new_machine(request, userid): messages.error(request, "Vous avez atteint le maximum d'interfaces autorisées que vous pouvez créer vous même (%s) " % max_lambdauser_interfaces) return redirect("/users/profil/" + str(request.user.id)) machine = NewMachineForm(request.POST or None) - interface = AddInterfaceForm(request.POST or None, infra=request.user.has_perms(('infra',))) + interface = AddInterfaceForm(request.POST or None, infra=request.user.has_perms(('infra',))) nb_machine = Interface.objects.filter(machine__user=userid).count() domain = DomainForm(request.POST or None, user=user, nb_machine=nb_machine) if machine.is_valid() and interface.is_valid(): @@ -950,7 +950,7 @@ def history(request, object, id): object_instance = Text.objects.get(pk=id) except Text.DoesNotExist: messages.error(request, "Text inexistant") - return redirect("/machines/") + return redirect("/machines/") elif object == 'ns' and request.user.has_perms(('cableur',)): try: object_instance = Ns.objects.get(pk=id) @@ -997,7 +997,7 @@ def history(request, object, id): @login_required @permission_required('cableur') def index_portlist(request): - port_list = OuverturePortList.objects.all().order_by('name') + port_list = OuverturePortList.objects.all().order_by('name') return render(request, "machines/index_portlist.html", {'port_list':port_list}) @login_required @@ -1010,7 +1010,7 @@ def edit_portlist(request, pk): return redirect("/machines/index_portlist/") port_list = EditOuverturePortListForm(request.POST or None, instance=port_list_instance) port_formset = modelformset_factory( - OuverturePort, + OuverturePort, fields=('begin','end','protocole','io'), extra=0, can_delete=True, @@ -1049,7 +1049,7 @@ def del_portlist(request, pk): def add_portlist(request): port_list = EditOuverturePortListForm(request.POST or None) port_formset = modelformset_factory( - OuverturePort, + OuverturePort, fields=('begin','end','protocole','io'), extra=0, can_delete=True, From b603c1fe7c7aa40b15a0e21dc09f873e81939314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 7 Oct 2017 17:45:22 +0000 Subject: [PATCH 33/44] =?UTF-8?q?Fix:=20r=C3=A9cup=C3=A8re=20plus=20propre?= =?UTF-8?q?ment=20l'id=20de=20l'instance=20en=20train=20d'=C3=AAtre=20?= =?UTF-8?q?=C3=A9dit=C3=A9e?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- machines/forms.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/machines/forms.py b/machines/forms.py index c9a3873f..940b5041 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -65,7 +65,7 @@ class EditInterfaceForm(ModelForm): self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4" self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).annotate(mtype_id=F('ip_type__machinetype__id')) # Add it's own address - self.fields['ipv4'].queryset |= IpList.objects.filter(id=self.fields['ipv4'].get_bound_field(self, 'ipv4').value()).annotate(mtype_id=F('ip_type__machinetype__id')) + self.fields['ipv4'].queryset |= IpList.objects.filter(id=self.instance.id).annotate(mtype_id=F('ip_type__machinetype__id')) if "machine" in self.fields: self.fields['machine'].queryset = Machine.objects.all().select_related('user') @@ -99,10 +99,10 @@ class BaseEditInterfaceForm(EditInterfaceForm): self.fields['type'].queryset = MachineType.objects.filter(ip_type__in=IpType.objects.filter(need_infra=False)) self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)).annotate(mtype_id=F('ip_type__machinetype__id')) # Add it's own address - self.fields['ipv4'].queryset |= IpList.objects.filter(id=self.fields['ipv4'].get_bound_field(self, 'ipv4').value()).annotate(mtype_id=F('ip_type__machinetype__id')) + self.fields['ipv4'].queryset |= IpList.objects.filter(id=self.instance.id).annotate(mtype_id=F('ip_type__machinetype__id')) else: self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).annotate(mtype_id=F('ip_type__machinetype__id')) - self.fields['ipv4'].queryset |= IpList.objects.filter(id=self.fields['ipv4'].get_bound_field(self, 'ipv4').value()).annotate(mtype_id=F('ip_type__machinetype__id')) + self.fields['ipv4'].queryset |= IpList.objects.filter(id=self.instance.id).annotate(mtype_id=F('ip_type__machinetype__id')) class AliasForm(ModelForm): class Meta: From 90b789d51bafa35a1578a1381cc2c167e59745d6 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Sat, 7 Oct 2017 20:17:17 +0200 Subject: [PATCH 34/44] Optimisation sur la methode qui renvoie les interfaces d'un user --- users/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/users/models.py b/users/models.py index 3a0af0f0..78b76156 100644 --- a/users/models.py +++ b/users/models.py @@ -378,7 +378,7 @@ class User(AbstractBaseUser): def user_interfaces(self, active=True): """ Renvoie toutes les interfaces dont les machines appartiennent à self Par defaut ne prend que les interfaces actives""" - return Interface.objects.filter(machine__in=Machine.objects.filter(user=self, active=active)) + return Interface.objects.filter(machine__in=Machine.objects.filter(user=self, active=active)).select_related('domain__extension') def assign_ips(self): """ Assign une ipv4 aux machines d'un user """ From a8d0c914ca4556ccb2cb29d4958071c089481d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 7 Oct 2017 18:52:49 +0000 Subject: [PATCH 35/44] Met l'ip actuelle et la bonne value dans le form d'edition d'interface --- machines/forms.py | 6 +++--- machines/templatetags/bootstrap_form_typeahead.py | 14 +++++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/machines/forms.py b/machines/forms.py index 940b5041..48498d36 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -65,7 +65,7 @@ class EditInterfaceForm(ModelForm): self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4" self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).annotate(mtype_id=F('ip_type__machinetype__id')) # Add it's own address - self.fields['ipv4'].queryset |= IpList.objects.filter(id=self.instance.id).annotate(mtype_id=F('ip_type__machinetype__id')) + self.fields['ipv4'].queryset |= IpList.objects.filter(interface=self.instance).annotate(mtype_id=F('ip_type__machinetype__id')) if "machine" in self.fields: self.fields['machine'].queryset = Machine.objects.all().select_related('user') @@ -99,10 +99,10 @@ class BaseEditInterfaceForm(EditInterfaceForm): self.fields['type'].queryset = MachineType.objects.filter(ip_type__in=IpType.objects.filter(need_infra=False)) self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)).annotate(mtype_id=F('ip_type__machinetype__id')) # Add it's own address - self.fields['ipv4'].queryset |= IpList.objects.filter(id=self.instance.id).annotate(mtype_id=F('ip_type__machinetype__id')) + self.fields['ipv4'].queryset |= IpList.objects.filter(interface=self.instance).annotate(mtype_id=F('ip_type__machinetype__id')) else: self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).annotate(mtype_id=F('ip_type__machinetype__id')) - self.fields['ipv4'].queryset |= IpList.objects.filter(id=self.instance.id).annotate(mtype_id=F('ip_type__machinetype__id')) + self.fields['ipv4'].queryset |= IpList.objects.filter(interface=self.instance).annotate(mtype_id=F('ip_type__machinetype__id')) class AliasForm(ModelForm): class Meta: diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py index f4cd357d..39a3e33c 100644 --- a/machines/templatetags/bootstrap_form_typeahead.py +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -180,6 +180,7 @@ def bootstrap_form_typeahead(django_form, typeahead_fields, *args, **kwargs): typeahead_js( f_name, f_value, + f_bound, t_choices, t_engine, t_match_func, @@ -215,7 +216,7 @@ def hidden_tag( f_bound, f_name ): } ) -def typeahead_js( f_name, f_value, +def typeahead_js( f_name, f_value, f_bound, t_choices, t_engine, t_match_func, t_update_on ) : """ The whole script to use """ @@ -239,7 +240,7 @@ def typeahead_js( f_name, f_value, '$("#'+input_id(f_name) + '").typeahead(\n' + \ default_datasets( f_name, match_func ) + '\n' + \ ');\n' + \ - reset_input( f_name, f_value ) + '\n' + \ + reset_input( f_name, f_bound ) + '\n' + \ '};\n' + \ '$("#'+input_id(f_name) + '").bind(\n' + \ '"typeahead:select", ' + \ @@ -254,10 +255,13 @@ def typeahead_js( f_name, f_value, return render_tag( 'script', content=mark_safe( js_content ) ) -def reset_input( f_name, f_value ) : +def reset_input( f_name, f_bound ) : """ The JS script to reset the fields values """ - return '$("#'+input_id(f_name)+'").typeahead("val","");\n' \ - '$("#'+hidden_id(f_name)+'").val("");' + return '$("#'+input_id(f_name)+'").typeahead(' \ + '"val", ' \ + 'engine_'+f_name+'.get('+str(f_bound.value())+')[0].value' \ + ');\n' \ + '$("#'+hidden_id(f_name)+'").val('+str(f_bound.value())+');' def default_choices( f_value ) : """ The JS script creating the variable choices_ """ From f397fab16035707a23fb91a36182b6b5f0ed7e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 7 Oct 2017 21:57:45 +0000 Subject: [PATCH 36/44] Fix: JS BFT: Pas besoin de class pour un div sans elt visible --- machines/templatetags/bootstrap_form_typeahead.py | 1 - 1 file changed, 1 deletion(-) diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py index 39a3e33c..b43af2c5 100644 --- a/machines/templatetags/bootstrap_form_typeahead.py +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -175,7 +175,6 @@ def bootstrap_form_typeahead(django_form, typeahead_fields, *args, **kwargs): ) form += render_tag( 'div', - attrs = {'class': 'form-group'}, content = hidden_tag( f_bound, f_name ) + typeahead_js( f_name, From 07c83384858c3c357a586b6cda485255fa5bc72a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 7 Oct 2017 22:12:48 +0000 Subject: [PATCH 37/44] =?UTF-8?q?Modifie=20la=20valeur=20par=20d=C3=A9faul?= =?UTF-8?q?t=20du=20hidden=20input=20=C3=A0=20""=20si=20aucune=20value?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Permet la compatibilité sans JS car sinon il y avait None dans le champs comme on ne lancait pas la fonction de reset --- machines/templatetags/bootstrap_form_typeahead.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py index b43af2c5..a5c58d00 100644 --- a/machines/templatetags/bootstrap_form_typeahead.py +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -211,7 +211,7 @@ def hidden_tag( f_bound, f_name ): 'id': hidden_id(f_name), 'name': f_name, 'type': 'hidden', - 'value': f_bound.value() + 'value': f_bound.value() or "" } ) From 69ffc04d6a44c6573c2178ed9328f7c1e678085f Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Sun, 8 Oct 2017 00:21:46 +0200 Subject: [PATCH 38/44] Doc sur views machines --- machines/views.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/machines/views.py b/machines/views.py index 84b13d89..06b17c3f 100644 --- a/machines/views.py +++ b/machines/views.py @@ -155,6 +155,9 @@ def generate_ipv4_bft_param( form, is_type_tt ): @login_required def new_machine(request, userid): + """ Fonction de creation d'une machine. Cree l'objet machine, le sous objet interface et l'objet domain + à partir de model forms. + Trop complexe, devrait être simplifié""" try: user = User.objects.get(pk=userid) except User.DoesNotExist: @@ -201,6 +204,8 @@ def new_machine(request, userid): @login_required def edit_interface(request, interfaceid): + """ Edition d'une interface. Distingue suivant les droits les valeurs de interfaces et machines que l'user peut modifier + infra permet de modifier le propriétaire""" try: interface = Interface.objects.get(pk=interfaceid) except Interface.DoesNotExist: @@ -239,6 +244,7 @@ def edit_interface(request, interfaceid): @login_required def del_machine(request, machineid): + """ Supprime une machine, interfaces en mode cascade""" try: machine = Machine.objects.get(pk=machineid) except Machine.DoesNotExist: @@ -258,6 +264,7 @@ def del_machine(request, machineid): @login_required def new_interface(request, machineid): + """ Ajoute une interface et son domain associé à une machine existante""" try: machine = Machine.objects.get(pk=machineid) except Machine.DoesNotExist: @@ -296,6 +303,7 @@ def new_interface(request, machineid): @login_required def del_interface(request, interfaceid): + """ Supprime une interface. Domain objet en mode cascade""" try: interface = Interface.objects.get(pk=interfaceid) except Interface.DoesNotExist: @@ -319,6 +327,7 @@ def del_interface(request, interfaceid): @login_required @permission_required('infra') def add_iptype(request): + """ Ajoute un range d'ip. Intelligence dans le models, fonction views minimaliste""" iptype = IpTypeForm(request.POST or None) if iptype.is_valid(): with transaction.atomic(), reversion.create_revision(): @@ -332,6 +341,7 @@ def add_iptype(request): @login_required @permission_required('infra') def edit_iptype(request, iptypeid): + """ Edition d'un range. Ne permet pas de le redimensionner pour éviter l'incohérence""" try: iptype_instance = IpType.objects.get(pk=iptypeid) except IpType.DoesNotExist: @@ -350,6 +360,7 @@ def edit_iptype(request, iptypeid): @login_required @permission_required('infra') def del_iptype(request): + """ Suppression d'un range ip. Supprime les objets ip associés""" iptype = DelIpTypeForm(request.POST or None) if iptype.is_valid(): iptype_dels = iptype.cleaned_data['iptypes'] From 9d4d67e0322eef5e2f74b128832fe1e7a107a8ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sun, 8 Oct 2017 00:48:32 +0000 Subject: [PATCH 39/44] Proprification de code du bft tag + fix mineur MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Utilise la fonction .format plutôt que la concténation pour rendre le code plus lisible. Le fix concernait le cas où il n'y a pas de initial_value, le JS sortait une erreur --- .../templatetags/bootstrap_form_typeahead.py | 192 +++++++++++------- machines/views.py | 60 +++--- 2 files changed, 147 insertions(+), 105 deletions(-) diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py index a5c58d00..5f10f5f6 100644 --- a/machines/templatetags/bootstrap_form_typeahead.py +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -219,112 +219,150 @@ def typeahead_js( f_name, f_value, f_bound, t_choices, t_engine, t_match_func, t_update_on ) : """ The whole script to use """ - choices = mark_safe(t_choices[f_name]) if f_name in t_choices.keys() \ - else default_choices( f_value ) + choices = mark_safe( t_choices[f_name] ) if f_name in t_choices.keys() \ + else default_choices( f_value ) - engine = mark_safe(t_engine[f_name]) if f_name in t_engine.keys() \ - else default_engine ( f_name ) + engine = mark_safe( t_engine[f_name] ) if f_name in t_engine.keys() \ + else default_engine ( f_name ) match_func = mark_safe(t_match_func[f_name]) \ - if f_name in t_match_func.keys() \ - else default_match_func( f_name ) + if f_name in t_match_func.keys() else default_match_func( f_name ) update_on = t_update_on[f_name] if f_name in t_update_on.keys() else [] - js_content = \ - 'var choices_'+f_name+' = ' + choices + ';\n' + \ - 'var setup_'+f_name+' = function() {\n' + \ - 'var engine_'+f_name+' = ' + engine + ';\n' + \ - '$("#'+input_id(f_name) + '").typeahead("destroy");\n' + \ - '$("#'+input_id(f_name) + '").typeahead(\n' + \ - default_datasets( f_name, match_func ) + '\n' + \ - ');\n' + \ - reset_input( f_name, f_bound ) + '\n' + \ - '};\n' + \ - '$("#'+input_id(f_name) + '").bind(\n' + \ - '"typeahead:select", ' + \ - typeahead_updater( f_name ) + '\n' + \ - ').bind(\n' + \ - '"typeahead:change", ' + \ - typeahead_change( f_name ) + '\n' + \ - ');\n' - for u_id in update_on : - js_content += '$("#'+u_id+'").change( setup_'+f_name+' );\n' - js_content += '$("#'+input_id(f_name)+'").ready( setup_'+f_name+' );\n' + js_content = ( + 'var choices_{f_name} = {choices};' + 'var setup_{f_name} = function() {{' + 'var engine_{f_name} = {engine};' + '$( "#{input_id}" ).typeahead( "destroy" );' + '$( "#{input_id}" ).typeahead( {datasets} );' + '{reset_input}' + '}};' + '$( "#{input_id}" ).bind( "typeahead:select", {updater} );' + '$( "#{input_id}" ).bind( "typeahead:change", {change} );' + '{updates}' + '$( "#{input_id}" ).ready( setup_{f_name} );' + ).format( + f_name = f_name, + choices = choices, + engine = engine, + input_id = input_id( f_name ), + datasets = default_datasets( f_name, match_func ), + reset_input = reset_input( f_name, f_bound ), + updater = typeahead_updater( f_name ), + change = typeahead_change( f_name ), + updates = ''.join( + ['$( "#{u_id}").change( setup_{f_name} );'.format( + u_id = u_id, + f_name = f_name + ) for u_id in update_on ] + ) + ) return render_tag( 'script', content=mark_safe( js_content ) ) def reset_input( f_name, f_bound ) : """ The JS script to reset the fields values """ - return '$("#'+input_id(f_name)+'").typeahead(' \ - '"val", ' \ - 'engine_'+f_name+'.get('+str(f_bound.value())+')[0].value' \ - ');\n' \ - '$("#'+hidden_id(f_name)+'").val('+str(f_bound.value())+');' + init_key = f_bound.value() or '""' + return ( + '$( "#{input_id}" ).typeahead("val", {init_val});' + '$( "#{hidden_id}").val( {init_key} );' + ).format( + input_id = input_id( f_name ), + init_val = '""' if init_key == '""' else + 'engine_{f_name}.get( {init_key} )[0].value'.format( + f_name = f_name, + init_key = init_key + ), + init_key = init_key, + hidden_id = hidden_id( f_name ) + ) def default_choices( f_value ) : """ The JS script creating the variable choices_ """ - return '[' + \ - ', '.join([ \ - '{key: ' + (str(choice[0]) if choice[0] != '' else '""') + \ - ', value: "' + str(choice[1]) + '"}' \ - for choice in f_value.choices \ - ]) + \ - ']' + return '[ {objects} ]'.format( + objects = ', '.join( + [ '{{ key: {k}, value: "{k}" }}'.format( + k = choice[0] if choice[0] != '' else '""', + v = choice[1] + ) for choice in f_value.choices ] + ) + ) def default_engine ( f_name ) : """ The JS script creating the variable engine_ """ - return 'new Bloodhound({ ' \ - 'datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"), ' \ - 'queryTokenizer: Bloodhound.tokenizers.whitespace, ' \ - 'local: choices_'+f_name+', ' \ - 'identify: function(obj) { return obj.key; } ' \ - '})' + return ( + 'new Bloodhound({{' + 'datumTokenizer: Bloodhound.tokenizers.obj.whitespace("value"),' + 'queryTokenizer: Bloodhound.tokenizers.whitespace,' + 'local: choices_{f_name},' + 'identify: function(obj) {{ return obj.key; }}' + '}})' + ).format( + f_name = f_name + ) def default_datasets( f_name, match_func ) : """ The JS script creating the datasets to use with typeahead """ - return '{ ' \ - 'hint: true, ' \ - 'highlight: true, ' \ - 'minLength: 0 ' \ - '}, ' \ - '{ ' \ - 'display: "value", ' \ - 'name: "'+f_name+'", ' \ - 'source: '+match_func + \ - '}' + return ( + '{{' + 'hint: true,' + 'highlight: true,' + 'minLength: 0' + '}},' + '{{' + 'display: "value",' + 'name: "{f_name}",' + 'source: {match_func}' + '}}' + ).format( + f_name = f_name, + match_func = match_func + ) def default_match_func ( f_name ) : """ The JS script creating the matching function to use with typeahed """ - return 'function(q, sync) {' \ - 'if (q === "") {' \ - 'var nb = 10;' \ - 'var first = [] ;' \ - 'for ( var i=0 ; i Date: Sun, 8 Oct 2017 01:29:50 +0000 Subject: [PATCH 40/44] Fix: BFT Tag : JS fail sur le reset des input quand init_val != "" Ajoute une fonction init_input qui fait ce que faisait reset_input avant et maintenant reset_input, se contente de mettre "" dans les input --- .../templatetags/bootstrap_form_typeahead.py | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py index 5f10f5f6..e076b52c 100644 --- a/machines/templatetags/bootstrap_form_typeahead.py +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -232,41 +232,49 @@ def typeahead_js( f_name, f_value, f_bound, js_content = ( 'var choices_{f_name} = {choices};' + 'var engine_{f_name};' 'var setup_{f_name} = function() {{' - 'var engine_{f_name} = {engine};' + 'engine_{f_name} = {engine};' '$( "#{input_id}" ).typeahead( "destroy" );' '$( "#{input_id}" ).typeahead( {datasets} );' - '{reset_input}' '}};' '$( "#{input_id}" ).bind( "typeahead:select", {updater} );' '$( "#{input_id}" ).bind( "typeahead:change", {change} );' '{updates}' - '$( "#{input_id}" ).ready( setup_{f_name} );' + '$( "#{input_id}" ).ready( function() {{' + 'setup_{f_name}();' + '{init_input}' + '}} );' ).format( f_name = f_name, choices = choices, engine = engine, input_id = input_id( f_name ), datasets = default_datasets( f_name, match_func ), - reset_input = reset_input( f_name, f_bound ), updater = typeahead_updater( f_name ), change = typeahead_change( f_name ), - updates = ''.join( - ['$( "#{u_id}").change( setup_{f_name} );'.format( + updates = ''.join( [ ( + '$( "#{u_id}" ).change( function() {{' + 'setup_{f_name}();' + '{reset_input}' + '}} );' + ).format( u_id = u_id, + reset_input = reset_input( f_name ), f_name = f_name ) for u_id in update_on ] - ) + ), + init_input = init_input( f_name, f_bound ), ) return render_tag( 'script', content=mark_safe( js_content ) ) -def reset_input( f_name, f_bound ) : - """ The JS script to reset the fields values """ +def init_input( f_name, f_bound ) : + """ The JS script to init the fields values """ init_key = f_bound.value() or '""' return ( '$( "#{input_id}" ).typeahead("val", {init_val});' - '$( "#{hidden_id}").val( {init_key} );' + '$( "#{hidden_id}" ).val( {init_key} );' ).format( input_id = input_id( f_name ), init_val = '""' if init_key == '""' else @@ -278,6 +286,16 @@ def reset_input( f_name, f_bound ) : hidden_id = hidden_id( f_name ) ) +def reset_input( f_name ) : + """ The JS script to reset the fields values """ + return ( + '$( "#{input_id}" ).typeahead("val", "");' + '$( "#{hidden_id}" ).val( "" );' + ).format( + input_id = input_id( f_name ), + hidden_id = hidden_id( f_name ) + ) + def default_choices( f_value ) : """ The JS script creating the variable choices_ """ return '[ {objects} ]'.format( From 5afd1badaafce53238f9b6257c8dc3be4f35f3d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sun, 8 Oct 2017 02:01:45 +0000 Subject: [PATCH 41/44] =?UTF-8?q?Utilisation=20du=20BFT=20tag=20sur=20les?= =?UTF-8?q?=20machines=20dans=20l'=C3=A9dition=20d'interface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit +Corretion d'une typo sur la génération de la variable choices --- machines/templates/machines/machine.html | 8 ++++++++ machines/templatetags/bootstrap_form_typeahead.py | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/machines/templates/machines/machine.html b/machines/templates/machines/machine.html index 4b308a11..d34dccb9 100644 --- a/machines/templates/machines/machine.html +++ b/machines/templates/machines/machine.html @@ -49,9 +49,17 @@ with this program; if not, write to the Free Software Foundation, Inc., {% if interfaceform %}

Interface

{% if i_bft_param %} + {% if 'machine' in interfaceform.fields %} + {% bootstrap_form_typeahead interfaceform 'ipv4,machine' bft_param=i_bft_param %} + {% else %} {% bootstrap_form_typeahead interfaceform 'ipv4' bft_param=i_bft_param %} + {% endif %} {% else %} + {% if 'machine' in interfaceform.fields %} + {% bootstrap_form_typeahead interfaceform 'ipv4,machine' %} + {% else %} {% bootstrap_form_typeahead interfaceform 'ipv4' %} + {% endif %} {% endif %} {% endif %} {% if domainform %} diff --git a/machines/templatetags/bootstrap_form_typeahead.py b/machines/templatetags/bootstrap_form_typeahead.py index e076b52c..05dd3147 100644 --- a/machines/templatetags/bootstrap_form_typeahead.py +++ b/machines/templatetags/bootstrap_form_typeahead.py @@ -298,9 +298,9 @@ def reset_input( f_name ) : def default_choices( f_value ) : """ The JS script creating the variable choices_ """ - return '[ {objects} ]'.format( - objects = ', '.join( - [ '{{ key: {k}, value: "{k}" }}'.format( + return '[{objects}]'.format( + objects = ','.join( + [ '{{key:{k},value:"{v}"}}'.format( k = choice[0] if choice[0] != '' else '""', v = choice[1] ) for choice in f_value.choices ] From 7c372d04bbff128cb9b4c52246af7d5dd2110274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sun, 8 Oct 2017 15:11:24 +0000 Subject: [PATCH 42/44] =?UTF-8?q?Fix=20:=20enl=C3=A8ve=20le=20annotate=20d?= =?UTF-8?q?u=20form=20pour=20ne=20l'utiliser=20que=20dans=20le=20view?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit annotate(...) duplique les ip quand un ip_type est lié à plusieurs machine_type donc le form avait plusieurs fois la même ip (même id) dans les résultats de son queryset --- machines/forms.py | 18 +++++++++--------- machines/views.py | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/machines/forms.py b/machines/forms.py index 48498d36..63539888 100644 --- a/machines/forms.py +++ b/machines/forms.py @@ -29,7 +29,7 @@ import re from django.forms import ModelForm, Form, ValidationError from django import forms from .models import Domain, Machine, Interface, IpList, MachineType, Extension, Mx, Text, Ns, Service, Vlan, Nas, IpType, OuverturePortList, OuverturePort -from django.db.models import Q, F +from django.db.models import Q from django.core.validators import validate_email from users.models import User @@ -63,9 +63,9 @@ class EditInterfaceForm(ModelForm): self.fields['type'].empty_label = "Séléctionner un type de machine" if "ipv4" in self.fields: self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4" - self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).annotate(mtype_id=F('ip_type__machinetype__id')) + self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True) # Add it's own address - self.fields['ipv4'].queryset |= IpList.objects.filter(interface=self.instance).annotate(mtype_id=F('ip_type__machinetype__id')) + self.fields['ipv4'].queryset |= IpList.objects.filter(interface=self.instance) if "machine" in self.fields: self.fields['machine'].queryset = Machine.objects.all().select_related('user') @@ -79,9 +79,9 @@ class AddInterfaceForm(EditInterfaceForm): self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4" if not infra: self.fields['type'].queryset = MachineType.objects.filter(ip_type__in=IpType.objects.filter(need_infra=False)) - self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)).annotate(mtype_id=F('ip_type__machinetype__id')) + self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)) else: - self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).annotate(mtype_id=F('ip_type__machinetype__id')) + self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True) class NewInterfaceForm(EditInterfaceForm): class Meta(EditInterfaceForm.Meta): @@ -97,12 +97,12 @@ class BaseEditInterfaceForm(EditInterfaceForm): self.fields['ipv4'].empty_label = "Assignation automatique de l'ipv4" if not infra: self.fields['type'].queryset = MachineType.objects.filter(ip_type__in=IpType.objects.filter(need_infra=False)) - self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)).annotate(mtype_id=F('ip_type__machinetype__id')) + self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).filter(ip_type__in=IpType.objects.filter(need_infra=False)) # Add it's own address - self.fields['ipv4'].queryset |= IpList.objects.filter(interface=self.instance).annotate(mtype_id=F('ip_type__machinetype__id')) + self.fields['ipv4'].queryset |= IpList.objects.filter(interface=self.instance) else: - self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True).annotate(mtype_id=F('ip_type__machinetype__id')) - self.fields['ipv4'].queryset |= IpList.objects.filter(interface=self.instance).annotate(mtype_id=F('ip_type__machinetype__id')) + self.fields['ipv4'].queryset = IpList.objects.filter(interface__isnull=True) + self.fields['ipv4'].queryset |= IpList.objects.filter(interface=self.instance) class AliasForm(ModelForm): class Meta: diff --git a/machines/views.py b/machines/views.py index 0ebde631..ac37d8c6 100644 --- a/machines/views.py +++ b/machines/views.py @@ -36,7 +36,7 @@ from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.template import Context, RequestContext, loader from django.contrib import messages from django.contrib.auth.decorators import login_required, permission_required -from django.db.models import ProtectedError +from django.db.models import ProtectedError, F from django.forms import ValidationError, modelformset_factory from django.db import transaction from django.contrib.auth import authenticate, login @@ -92,7 +92,7 @@ def generate_ipv4_choices( form ) : choices = '{"":[{key:"",value:"Choisissez d\'abord un type de machine"},' mtype_id = -1 - for ip in f_ipv4.queryset.order_by('mtype_id', 'id') : + for ip in f_ipv4.queryset.annotate(mtype_id=F('ip_type__machinetype__id')).order_by('mtype_id', 'id') : if mtype_id != ip.mtype_id : mtype_id = ip.mtype_id used_mtype_id.append(mtype_id) From fad9fd0dcd62f2fafadc983adbd2b993760fe4ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Kervella?= Date: Sat, 7 Oct 2017 21:48:23 +0000 Subject: [PATCH 43/44] Nouvel affichage pour les machines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'ancien affichage commençait à prendre trop de place donc il fallait réarranger les infos --- machines/templates/machines/aff_machines.html | 88 ++++++++++--------- 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/machines/templates/machines/aff_machines.html b/machines/templates/machines/aff_machines.html index 3e26f64b..80e887d2 100644 --- a/machines/templates/machines/aff_machines.html +++ b/machines/templates/machines/aff_machines.html @@ -27,30 +27,36 @@ with this program; if not, write to the Free Software Foundation, Inc., {% endif %} + + + + + + + - - - - - - - - - - + + + + + + {% for machine in machines_list %} - {% for interface in machine.interface_set.all %} - - {% if forloop.first %} - + + - - {% endif %} + + {% for interface in machine.interface_set.all %} + - - - + + + {% endfor %} +
ActionsProprietaireNom dnsTypeMacIP
Nom DNSTypeMACIPActions
+
+ {{ machine.name }} + + {{ machine.user }} + + {% include 'buttons/add.html' with href='machines:new-interface' id=machine.id desc='Ajouter une interface' %} - {% include 'buttons/suppr.html' with href='machines:del-machine' id=machine.id %} {% include 'buttons/history.html' with href='machines:history' name='machine' id=machine.id %} + {% include 'buttons/suppr.html' with href='machines:del-machine' id=machine.id %} - {{ machine.user }} -
{% if interface.domain.related_domain.all %} {{ interface.type }}{{ interface.mac_address }}IPv4 {{ interface.ipv4 }} - {% if ipv6_enabled %} -
- IPv6 {{ interface.ipv6 }} - {% endif %} -
- + {{ interface.mac_address }} + + IPv4 {{ interface.ipv4 }} +
+ {% if ipv6_enabled and interface.ipv6 != 'None'%} + IPv6 {{ interface.ipv6 }} + {% endif %} +
+
From 13094d5be51397994111e46eb888aeeba57bfc42 Mon Sep 17 00:00:00 2001 From: Gabriel Detraz Date: Mon, 9 Oct 2017 01:29:42 +0200 Subject: [PATCH 44/44] Optimisation sur les select related --- machines/views.py | 12 ++++++------ topologie/views.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/machines/views.py b/machines/views.py index ac37d8c6..b3f4eeb7 100644 --- a/machines/views.py +++ b/machines/views.py @@ -857,13 +857,13 @@ def index(request): @login_required @permission_required('cableur') def index_iptype(request): - iptype_list = IpType.objects.select_related('extension').order_by('type') + iptype_list = IpType.objects.select_related('extension').select_related('vlan').order_by('type') return render(request, 'machines/index_iptype.html', {'iptype_list':iptype_list}) @login_required @permission_required('cableur') def index_vlan(request): - vlan_list = Vlan.objects.order_by('vlan_id') + vlan_list = Vlan.objects.prefetch_related('iptype_set').order_by('vlan_id') return render(request, 'machines/index_vlan.html', {'vlan_list':vlan_list}) @login_required @@ -875,7 +875,7 @@ def index_machinetype(request): @login_required @permission_required('cableur') def index_nas(request): - nas_list = Nas.objects.select_related('machine_type').order_by('name') + nas_list = Nas.objects.select_related('machine_type').select_related('nas_type').order_by('name') return render(request, 'machines/index_nas.html', {'nas_list':nas_list}) @login_required @@ -903,8 +903,8 @@ def index_alias(request, interfaceid): @login_required @permission_required('cableur') def index_service(request): - service_list = Service.objects.all() - servers_list = Service_link.objects.all() + service_list = Service.objects.prefetch_related('service_link_set__server__domain__extension').all() + servers_list = Service_link.objects.select_related('server__domain__extension').select_related('service').all() return render(request, 'machines/index_service.html', {'service_list':service_list, 'servers_list':servers_list}) @login_required @@ -1012,7 +1012,7 @@ def history(request, object, id): @login_required @permission_required('cableur') def index_portlist(request): - port_list = OuverturePortList.objects.all().order_by('name') + port_list = OuverturePortList.objects.prefetch_related('ouvertureport_set').prefetch_related('interface_set').order_by('name') return render(request, "machines/index_portlist.html", {'port_list':port_list}) @login_required diff --git a/topologie/views.py b/topologie/views.py index eba00e0c..42cd09e7 100644 --- a/topologie/views.py +++ b/topologie/views.py @@ -45,7 +45,7 @@ from preferences.models import AssoOption, GeneralOption @permission_required('cableur') def index(request): """ Vue d'affichage de tous les swicthes""" - switch_list = Switch.objects.order_by('stack','stack_member_id','location').select_related('switch_interface__domain__extension').select_related('switch_interface__ipv4').select_related('switch_interface__domain') + switch_list = Switch.objects.order_by('stack','stack_member_id','location').select_related('switch_interface__domain__extension').select_related('switch_interface__ipv4').select_related('switch_interface__domain').select_related('stack') return render(request, 'topologie/index.html', {'switch_list': switch_list}) @login_required @@ -128,7 +128,7 @@ def index_room(request): @login_required @permission_required('infra') def index_stack(request): - stack_list = Stack.objects.order_by('name') + stack_list = Stack.objects.order_by('name').prefetch_related('switch_set__switch_interface__domain__extension') return render(request, 'topologie/index_stack.html', {'stack_list': stack_list})