source: main/waeup.sirp/trunk/src/waeup/sirp/widgets/phonewidget.py @ 7655

Last change on this file since 7655 was 7494, checked in by Henrik Bettermann, 13 years ago

Adjust size of phone widget. The default country code is still not being set.

  • Property svn:keywords set to Id
File size: 8.3 KB
Line 
1"""A phone number widget.
2
3This widget is an input widget (not made for display forms but for
4edit and add forms).
5
6It can be used for :class:`zope.schema.TextLine` fields but has to be
7requested by the form manually (otherwise the regular TextLine widget
8will be used for rendering).
9
10The code was mainly taken from usphone implementation in z3c.widget
11(which was designed for outdated zope.app.form (instead of
12zope.formlib or newer form libs). Therefore some modifications were
13neccessary and it might look a bit overloaded.
14
15If you use the PhoneWidget for rendering regular TextLine attributes
16(preferably in edit forms or add forms), the phone number is displayed
17by three input fields representing the international code, the area
18code and the extension line. All three fields require pure numbers as
19input and do not accept other chars.
20
21When the entered input is stored with a context object, it is stored
22as a single unicode string with the numbers divided by single hyphen.
23
24So, input <12>, <111>, <444> becomes the string ``'12-111-444'`` for
25the context object.
26
27Large parts of this module are copied from z3c.widget and may be
28covered by ZPL.
29
30"""
31import re
32import copy
33from waeup.sirp import MessageFactory as _
34from zope import schema
35from zope.browserpage import ViewPageTemplateFile
36from zope.component import getMultiAdapter
37from zope.formlib import form
38from zope.formlib.interfaces import (
39    IBrowserWidget, IWidgetInputErrorView, IInputWidget, WidgetInputError,
40    MissingInputError)
41from zope.formlib.widget import SimpleInputWidget
42from zope.interface import Interface, implements
43from zope.schema.interfaces import RequiredMissing
44
45class IPhoneData(Interface):
46    """A schema used to generate a Phone widget."""
47
48    country = schema.TextLine(
49        title=_('Country Code'),
50        description=_('The country code of the phone number.'),
51        min_length=1,
52        constraint=re.compile(r'^[0-9]+$').search,
53        default=u'234', # int. code for nigeria
54        required=True)
55
56    area = schema.TextLine(
57        title=_('Area Code'),
58        description=_('The area code of the phone number.'),
59        min_length=1,
60        constraint=re.compile(r'^[0-9]+$').search,
61        required=True)
62
63    extension = schema.TextLine(
64        title=_('Direct Line'),
65        description=_('The direct line of the phone number.'),
66        min_length=3,
67        constraint=re.compile(r'^[0-9]{3,}$').search,
68        required=True)
69
70class PhoneWidgetData(object):
71    """Phone number data"""
72    implements(IPhoneData)
73
74    country = None
75    area = None
76    extension = None
77
78    def __init__(self, context, number=None):
79        """Set countrycode, areacode, and extension line from number.
80        """
81        self.context = context
82        if number is None:
83            return
84        # handle other types than strings.
85        if not isinstance(number, basestring):
86            number = str(number)
87        parts = number.split('-', 2)
88        if len(parts) == 2:
89            parts = [None,] + parts
90        elif len(parts) == 0:
91            return
92        elif len(parts) == 1:
93            parts = [None, None] + parts
94        self.country, self.area, self.extension = parts
95        return
96
97    @property
98    def number(self):
99        """Return a valid phone number string of format ``<IC>-<AC>-<EL>``.
100
101        where <IC> is the country code (digits only), <AC> is the area
102        code and <EL> is the extension line.
103        """
104        return u'-'.join(
105            (self.country or '', self.area or '', self.extension))
106
107
108class PhoneWidget(SimpleInputWidget):
109    """Phone Number Widget"""
110    implements(IBrowserWidget, IInputWidget)
111
112    template = ViewPageTemplateFile('phonewidget.pt')
113    _prefix = 'field.'
114    _error = None
115    widgets = {}
116
117    # See zope.formlib.interfaces.IWidget
118    name = None
119    visible = True
120
121    def __init__(self, field, request):
122        super(PhoneWidget, self).__init__(field, request)
123        value = field.query(field.context)
124        self._generateSubWidgets()
125        return
126
127    def _generateSubWidgets(self):
128        """Create the three subwidgets.
129        """
130        value = self.context.query(self.context.context)
131        # We have to modify the adapter according to our own
132        # 'required' state. Some ugly workaround
133        adapter = copy.deepcopy(IPhoneData)
134        for name in 'country', 'area', 'extension':
135            adapter[name].required = self.required
136        adapters = {adapter: PhoneWidgetData(self, value)}
137        self.widgets = form.setUpEditWidgets(
138            form.FormFields(IPhoneData),
139            self.name, value, self.request, adapters=adapters)
140        # The displayWidth cant'be set
141        # because it is overridden by Bootstrap css classes
142        #self.widgets['country'].displayWidth = 3
143        #self.widgets['area'].displayWidth = 5
144        #self.widgets['extension'].displayWidth = 10
145        # We have to set the cssClass attributes
146        self.widgets['country'].cssClass = 'span2'
147        self.widgets['area'].cssClass = 'span2'
148        self.widgets['extension'].cssClass = 'span3'
149
150        return
151
152    def setRenderedValue(self, value):
153        """See zope.formlib.interfaces.IWidget"""
154        if isinstance(value, unicode) and '-' in value:
155            country, area, extension = value.split('-', 2)
156            self.widgets['country'].setRenderedValue(country)
157            self.widgets['area'].setRenderedValue(area)
158            self.widgets['extension'].setRenderedValue(extension)
159            return
160        return
161
162    def setPrefix(self, prefix):
163        """See zope.formlib.interfaces.IWidget"""
164        # Set the prefix locally
165        if not prefix.endswith("."):
166            prefix += '.'
167        self._prefix = prefix
168        self.name = prefix + self.context.__name__
169        # Now distribute it to the sub-widgets
170        self._generateSubWidgets()
171        return
172
173    def getInputValue(self):
174        """See zope.formlib.interfaces.IInputWidget"""
175        self._error = None
176        try:
177            result = u'-'.join((
178                self.widgets['country'].getInputValue(),
179                self.widgets['area'].getInputValue(),
180                self.widgets['extension'].getInputValue() ))
181            return result
182        except TypeError:
183            if self.required:
184                return self._missing
185            return None
186        except ValueError, v:
187            self._error = WidgetInputError(
188                self.context.__name__, self.label, _(v))
189            raise self._error
190        except WidgetInputError, e:
191            self._error = e
192            raise e
193
194    def applyChanges(self, content):
195        """See zope.formlib.interfaces.IInputWidget"""
196        field = self.context
197        new_value = self.getInputValue()
198        old_value = field.query(content, self)
199        # The selection has not changed
200        if new_value == old_value:
201            return False
202        field.set(content, new_value)
203        return True
204
205    def hasInput(self):
206        """See zope.formlib.interfaces.IInputWidget"""
207        return (self.widgets['country'].hasInput() and
208                (self.widgets['area'].hasInput() and
209                 self.widgets['extension'].hasInput()))
210
211
212    def hasValidInput(self):
213        """See zope.formlib.interfaces.IInputWidget"""
214        return (self.widgets['country'].hasValidInput() and
215            self.widgets['area'].hasValidInput() and
216            self.widgets['extension'].hasValidInput())
217
218
219    def hidden(self):
220        """See zope.formlib.interfaces.IBrowserWidget"""
221        output = []
222        output.append(self.widgets['country'].hidden())
223        output.append(self.widgets['area'].hidden())
224        output.append(self.widgets['extension'].hidden())
225        return '\n'.join(output)
226
227
228    def error(self):
229        """See zope.formlib.interfaces.IBrowserWidget"""
230        if self._error:
231            return getMultiAdapter(
232                (self._error, self.request),
233                IWidgetInputErrorView).snippet()
234        country_error = self.widgets['country'].error()
235        if country_error:
236            return country_error
237        area_error = self.widgets['area'].error()
238        if area_error:
239            return area_error
240        extension_error = self.widgets['extension'].error()
241        if extension_error:
242            return extension_error
243        return ""
244
245    def __call__(self):
246        """See zope.formlib.interfaces.IBrowserWidget"""
247        return self.template()
Note: See TracBrowser for help on using the repository browser.