1 2 3 4 5 6 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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680
|
The SkinScript Language Reference
Basic Concepts and Syntax
If you're familiar with ZPatterns, you know that DataSkins get their
attributes and behavior from AttributeProviders and RuleAgents,
respectively. Providers and Agents (known as DataPlugIns) can be
written directly in Python, but it is much easier to write them in
SkinScript. SkinScript lets you define an individual attribute
provider or rule agent in a single declarative statement, using
standard Zope tools such as SQL Methods, DTML Methods, Python
Methods, and so on to do the actual data retrieval or storage. In
effect, SkinScript is a "glue language" which lets you define the
linkages between a DataSkin and the methods you want to implement
its "skeleton" of data storage and triggered behavior.
In most languages, the order that statements appear in makes a
difference to the results you get, and SkinScript is no exception.
Although each SkinScript statement defines a seperate and
independent data plug-in, the ordering makes a difference in how
they will be used by DataSkins. When a DataSkin needs to perform
an operation (such as retrieving an attribute), it asks its
DataManager for a list of suitable DataPlugIns. This list is
ordered according to the original order of plug-ins as listed on the
Data Plug-ins tab of the Racks or Customizers involved. And the
declarations of a SkinScript Method are treated as though they were
individual plug-ins appearing in place of the SkinScript Method in
that list.
In other words, SkinScript is literally a language for defining
data plug-ins. A SkinScript *script* consists of a series of
*declarations*, seperated by whitespace. Each declaration is
compiled into a single data plug-in. SkinScript is a
keyword-driven language, is case-sensitive, and is not whitespace
sensitive, even for Python expressions contained within
declarations. All whitespace which occurs outside of quoted strings
is treated as though it were a single space. (Incidentally, this
means that the usual Python rules about where you can put linebreaks
in expressions do not apply. You can write the expression '1+2'
split across three lines, if you so desire.) Comments are marked as
in Python, using a '#' symbol to mean that the rest of the current
line is a comment. Comments are treated as simple whitespace.
Declarations
Declarations are the basic building block of SkinScript.
Declarations are compiled into AttributeProviders or RuleAgents, one
per declaration. Some declarations provide attribute values:
- 'INITIALIZE OBJECT WITH' *assignmentlist*
- 'WITH [QUERY]' *expression* 'COMPUTE' *nameorassignlist* [OTHERWISE LET assignmentlist] [DEPENDENT ON dependencies]
- 'WITH SELF COMPUTE' *assignmentlist*
Some handle attribute storage:
- [WHEN eventspec] 'STORE' *attributelist* 'USING' *expression* [ 'SAVING' *mementolist* ]
- 'STORE' *attributelist* 'IN SELF'
And others can call an expression upon transaction commit:
- 'WHEN' *eventspec* 'CALL' *expression* [ 'SAVING' *mementolist* ]
Each declaration has its own parameters, but there are certain
conventions which are followed across most kinds of declarations.
Declaration Parameters
*expression* -- A DTML-style Python expression. As with DTML, the
"_" object is available for access to Python built-ins and library
functions. Please see the section below on "Variables and Functions
Available in Expressions" for details on how names other than "_"
are looked up in SkinScript expressions.
*assignmentlist* -- A comma-seperated list of assignments in the
form *attributename* = *expression*, similar to passing keyword
arguments to a function or method.
*nameorassignlist* -- Similar to *assignmentlist*, but with a
special shorthand for the case where *attributename* and
*expression* are the same (e.g. 'foo=foo'). To make such an
assignment, you can replace *attributename* '=' *expression* with
just *attributename*. So 'foo=bar,baz=baz,bada=bing' can be
simplified to 'foo=bar,baz,bada=bing' if a clause's syntax allows a
*nameorassignmentlist*.
*mementolist* -- A *mementolist* is syntactically identical to a
*nameorassignlist*, but has a different context and purpose. A
*mementolist* is used to save old values of expressions for later
comparison purposes when a (sub)transaction commits, and appears
only in 'SAVING' clauses. Expression variables are looked up in the
context of the DataSkin whose snapshot is being taken. So in the
clause 'SAVING bar,foo=baz' the DataSkin's 'bar' attribute will be
saved as OLD['bar'], and its 'baz' attribute will be saved as
OLD['foo'].
A *mementolist* is computed only once per (sub)transaction for a
given DataSkin. The first time the DataSkin is changed in a
(sub)transaction, the 'SAVING' clause is executed for all
declarations that have one, even if the declaration never ends up
firing.
*eventspec* -- A clause of the form 'OBJECT ADDED, CHANGED,
DELETED', where the 'ADDED', 'CHANGED' and 'DELETED' keywords may be
used in any combination. For example, 'OBJECT ADDED,DELETED' and
'OBJECT CHANGED' are both valid *eventspec* clauses. Please see
HowTriggersWork for details on how ZPatterns events are interpreted
by SkinScript.
*attributelist* -- A comma-seperated list of attribute names, to
which the declaration will be applied. An asterisk ("*") may be
used as a wildcard attribute name, meaning that the declaration will
be applicable for any attribute name. Remember, however, that
declarations associated with specific names will take precedence
over wildcard declarations, even if the wildcard declaration comes
before the specific declaration and would match the name being
looked up.
*dependencies* -- A comma-seperated list of attribute names which,
when changed, will cause the dependent attributes to be recalculated
on their next access. Wildcards cannot be used, only actual
attribute names.
Variables and Functions Available in Expressions
Names used in an expression are usually looked up from the
attributes of individual SkinScript statement and its acquisition
parents (e.g. the Rack or Customizer which it's contained in, and so
on up the line). One exception to this rule is in the 'COMPUTE'
clause of a WITH/COMPUTE declaration, where names are first looked
up in the 'RESULT' object returned by the 'WITH' clause. The other
exception is in 'SAVING' clauses, where names are looked up in the
context of the DataSkin whose snapshot is being taken.
The following variable names and functions are provided by
SkinScript for use in expressions.
* Generally available:
'self' -- The DataSkin instance which the declaration is being
applied to at the time the expression is being called.
'NOT_FOUND' -- A special value which causes an attribute to be
non-existent, if this is the value provided. In a WITH/COMPUTE
declaration, if the 'WITH' expression returns 'NOT_FOUND', the
'COMPUTE' clause is ignored, and the 'OTHERWISE LET' clause is
activated if one exists.
* Available for WITH/COMPUTE declarations:
'RESULT' -- The result of the 'WITH' expression in a WITH/COMPUTE
declaration. Available only from expressions in the 'COMPUTE'
clause. This variable is placed atop the DTML namespace stack
during execution of the 'COMPUTE' clause, so just referring to a
name in a 'COMPUTE' expression will look up that name in the
'RESULT' object first. Only if it is not found there, will the
search continue to the declaration and its acquisition context.
'ATTRIBUTE_NAME' -- A string containing the name of the attribute
which the DataSkin is currently trying to retrieve. Available in
both the 'WITH' and 'COMPUTE' clauses.
* Available for WHEN/STORE and WHEN/CALL declarations:
'TRIGGER_EVENT' -- A string, either "ADD", "CHANGE", or "DELETE",
denoting the type of event which caused the expression to be
executed.
'OLD["name"]' -- 'OLD' is a mapping object containing the values
saved by the "SAVING mementolist" clause of a WHEN/STORE or
WHEN/CALL declaration.
'HAS_CHANGED("name")' -- returns a true value if the attribute
named "name" has been changed or deleted during the current
(sub)transaction.
'CHANGED_ATTRS()' -- returns a list of the names of attributes
which have been changed or deleted during the current
(sub)transaction.
'ORIGINAL["name"]' -- returns the value of the attribute named
"name" before it was changed or deleted, or the 'NOT_FOUND' value
if the attribute did not exist before this (sub)transaction. A
'KeyError' will be raised if "name" was not changed/deleted, so
use 'HAS_CHANGED()' to check before using this.
INITIALIZE OBJECT WITH *assignmentlist*
What It's For
Providing default attribute values to newly created objects.
How It Works
This declaration assigns the specified attribute values to the
object when it is created. Due to limitations of the built-in Zope
event model, it is not possible to unequivocally determine when
DataSkins outside of Racks are created, so this declaration
applies only to objects created inside Racks. The
*assignmentlist* is executed in context of the DataSkin, so later
assignments can reference the values of earlier assignments.
Examples
- Constant values::
INITIALIZE OBJECT WITH foo=1, bar='baz', degrees=360,
radians = degrees / (180 / _.math.pi)
- Defaults from properties in acquisition context::
INITIALIZE OBJECT WITH foo=myfoo, bar=mybar
WITH [ QUERY ] *expression* COMPUTE *nameorassignlist* [ OTHERWISE LET *assignmentlist* ] [ DEPENDENT ON *dependencies* ]
What It's For
Providing readable values for computed attributes and/or
attributes which are loaded from an external data source (e.g. via
an LDAP or SQL query). WITH/COMPUTE statements minimize redundant
calculations by determining some attribute values **before** they
are actually needed, as soon as one of a set of related values are
needed. This is especially important where database retrievals
are involved. It would be very bad to have to issue an SQL query
for each attribute access, even if the result was then cached. So
a WITH/COMPUTE statement executes the expensive *expression* once,
and then caches the attributes computed by the *nameorassignlist*.
Notice, by the way, that this means you can do performance tuning
by changing the grouping of attributes in your WITH/COMPUTE
statements, so that computations which are very expensive and
infrequently used are placed in standalone statements, and so on.
And if you're using data from outer-joined SQL tables, you may
even decide to split them into seperate queries (one per table)
and corresponding WITH/COMPUTE statements, if some of the tables'
data is rarely used.
How It Works
When an attribute defined in the "nameorassignlist" is requested,
call "expression". If the result of expression (available as
'RESULT' in the "nameorassignlist" expressions) is not 'NOT_FOUND',
compute all attributes from nameorassignlist, in the order they are
listed, caching the results for the remainder of the
(sub)transaction (unless later reset by a change to a 'DEPENDENT
ON' attribute).
If the 'RESULT' is 'NOT_FOUND', the search for the attribute value
falls through to the next declaration (or attribute provider if the
SkinScript is finished). If there is an 'OTHERWISE LET' clause,
the assignments given there are computed and cached for the
remainder of the (sub)transaction. (This is to prevent repeated
execution of "expression" that will only result in further
failures.)
The optional 'QUERY' keyword states that "expression" is a query
method such as an LDAP or SQL query that returns a sequence of
objects. When the 'QUERY' keyword is used, the declaration
automatically takes the first item of the sequence to be used as
'RESULT', unless the sequence is empty, in which case it behaves as
though the 'RESULT' were 'NOT_FOUND'.
The optional 'DEPENDENT ON' clause declares that the computed
attributes depend on the listed attributes for their values, and
that if any of the 'DEPENDENT ON' attributes are overwritten, the
cached values should be reset. (And thus recomputed the next time
they are accessed.) So, if for example you had a ZODB-stored field
which was a key to be looked up in an RDBMS, listing the key field
in the 'DEPENDENT ON' clause would ensure that any access to the
RDBMS-stored fields after a change in key would return data from
the correct record. (Note, however, that the attributes are *only*
reset if a 'DEPENDENT ON' attribute is changed by being *written
to*. A change in the value calculated or retrieved by another
statement or provider has no effect on the attribute cache.)
Examples
- Simple SQL query. 'SomeSQLMethod' is an SQL Method (located
somewhere in the acquisition context of this SkinScript method)
that takes 'someparam' as a parameter to return a row by primary
key, and returns a result which contains the fields 'foo', 'bar',
and 'baz', which we want to use as attributes of the same names::
WITH QUERY SomeSQLMethod(someparam=self.id) COMPUTE foo,bar,baz
- SQL query with renaming and computations. Again, notice how the
'WITH' expression is computed in the acquisition context of the
SkinScript method itself, but the 'COMPUTE' expressions are in the
context of the result of the 'WITH' expression::
WITH QUERY GetRoomData(roomname=self.id) COMPUTE
labor_cost=labor_rate*labor_hours, material_cost=materials,
height=roomdim1, width=roomdim2
- A simple computed attribute. The formula will be computed the
first time the DataSkin needs to know its 'total_cost' attribute
in a given (sub)transaction, and again if the 'labor_cost' or
'material_cost' attributes are written to::
WITH self.labor_cost + self.material_cost COMPUTE total_cost = RESULT
DEPENDENT ON labor_cost,material_cost
- Interdependent computed attributes. The first time that 'area',
'square_yards', or 'price_per_sq_yd' is asked for in a
(sub)transaction, all three will be computed (and recomputed if the
non-calculated attributes are written to)::
WITH self.width * self.height COMPUTE
area = RESULT,
square_yards = self.area / 9,
price_per_sq_yd = self.total_cost / self.square_yards
# We have to list 'real' attributes, not intermediate values!
DEPENDENT ON width,height,labor_cost,material_cost
- Object re-mapping; convert data from some object in another part
of the application. Notice that we can even save a reference to
the original object so we can call methods on it later to save our
changed attributes back into it... (And of course, we can also
seperate more expensive and less-often used attribute computations
into other WITH self.original_object COMPUTE statements)::
WITH SomeSpecialist.getItem(self.id) COMPUTE
my_foo=its_foo, my_bar=its_bar, original_object=RESULT
WITH SELF COMPUTE *nameorassignlist* [ DEPENDENT ON *dependencies* ]
What It's For
A shorthand way of defining computed attributes. This variant
form of WITH/COMPUTE simply treats 'self' as 'RESULT', placing it
on the namespace stack for the execution of the
*nameorassignlist*.
How It Works
When an attribute defined in the "nameorassignlist" is requested,
compute all attributes from nameorassignlist, in the order they are
listed, caching the results for the remainder of the
(sub)transaction (unless reset by a change to a 'DEPENDENT ON'
attribute). The DataSkin itself is placed on the namespace stack
during execution of the *nameorassignlist*, so expressions can
refer to other attributes without having to prefix them with
'self.'. 'WITH SELF COMPUTE' executes slightly faster than an
otherwise equivalent 'WITH self COMPUTE' statement, and a lot
faster than repeating 'self.foo', 'self.bar', and so on in an
expression.
The optional 'DEPENDENT ON' clause declares that the computed
attributes depend on the listed attributes for their values, and
that if any of the 'DEPENDENT ON' attributes are overwritten, the
cached values should be reset. (And thus recomputed the next time
they are accessed.) This is helpful for ensuring that
calculation-based attributes always reflect the object's current
state. Note, however, that the attributes are *only* reset if a
'DEPENDENT ON' attribute is changed by being *written to*. A
change in the value calculated or retrieved by another statement or
provider has no effect on the attribute cache.
Examples
- Simple computed attribute. The formula will be computed the first
time the DataSkin needs to know its 'total_cost' attribute in a
given (sub)transaction. Notice how much shorter this is than the
equivalent example shown under the general WITH/COMPUTE examples.
It will also execute more quickly, although by an utterly
insignificant amount::
WITH SELF COMPUTE total_cost = labor_cost+material_cost
DEPENDENT ON labor_cost,material_cost
- Interdependent computed attributes. Again, it's much less
verbose, and a hair faster than its generic WITH/COMPUTE cousin::
WITH SELF COMPUTE
area = width * height,
square_yards = area / 9,
price_per_sq_yd = total_cost / square_yards
# We have to list 'real' attributes, not intermediate values!
DEPENDENT ON width,height,labor_cost,material_cost
[ WHEN *eventspec* ] STORE *attributelist* USING *expression* [ SAVING *mementolist* ]
What It's For
Storing/updating attributes in an external data source. The
*attributelist* can contain an asterisk ('*') to mean "all
attributes".
How It Works
When an attribute listed in "attributelist" is changed/deleted, the
new value is cached in the DataSkin until a (sub)transaction
commit occurs. At that time, "expression" is called, with the
'OLD[]' variable containing the values saved by "mementolist", if
specified. (See the section above on "Declaration Parameters" for
more details on how memento lists work.)
If the optional "WHEN eventspec" clause is used, "expression" will
only be called if the eventspec matches the object-level event.
(Note that this means that if you use the WHEN clause, you will
need declarations for each possible event for each attribute to
ensure that the attributes will be saved under all possible
circumstances.)
A STORE/USING declaration is compiled into a single Data Plug-in
with two functions: to act as an attribute setter, and to act as a
rule agent. The attribute setter part simply caches changes to
attributes until (sub)transaction commit time, when the rule agent
part becomes active. The rule agent part executes the 'USING'
expression if and only if one of the 'STORE' attributes have been
changed, and the WHEN clause (if any) is applicable to the
situation as of commit time. Note that the seperation of these
parts means that the USING expression can and will be called **even
if** more than one declaration (or other Attribute Provider)
handles storage for the same attributes! This is actually quite
useful when you have data that you want updated in more than one
back-end database, but it can be surprising if you expect
first-come, only-served behavior as is the case for attribute
getters.
There is one interesting side effect, however, if a wildcard ('*')
is used in the 'STORE' list. Since the '*' matches **any**
attribute, the 'USING' expression will be called at subtransaction
commit as long as **any** attribute has changed and the 'WHEN'
clause is applicable. Again, this is a bit different from
attribute getters and other attribute setters, where '*'
effectively means "any attribute that hasn't already been claimed
by another provider."
Examples
- Context-dependent method-driven storage reusing already-written
methods. In this example, we use different methods for each
situation, which we assume are already-written and which we do not
want to change for our specific situation. Notice that for the
'ADD' and 'DELETE' events we use 'WHEN ... CALL' declarations,
because we want the widget data to be added or deleted regardless
of whether the 'foo' or 'bar' attributes have specifically
changed::
WHEN OBJECT ADDED CALL
AddWidget(widget_id=self.id, foo_field=self.foo, wbar=self.bar)
WHEN OBJECT CHANGED STORE foo,bar USING
UpdateMethod(widget_id=self.id, foo_field=self.foo, wbar=self.bar)
WHEN OBJECT DELETED CALL DeleteWidget(widget_id=self.id)
- Object remapping. This example shows how to create a "virtual"
object whose attributes 'my_foo' and 'my_bar' are mapped to/from
another object from a different specialist ('SomeSpecialist')
using the same id value, but whose fields are named 'its_foo' and
'its_bar'. The example works by maintaining an 'original_object'
attribute which contains the object from the other specialist.
This is actually a pretty simple remapping example; a single
SkinScript script might contain multiple remappings like this to
combine parts of different objects into a whole. Also, this
example assumes that we are creating an object in 'SomeSpecialist'
for every DataSkin created, while common real-life uses will often
have the 'original_object' created on demand for an existing
DataSkin, or conversely, have the DataSkin created on demand for
an existing 'original_object'::
# Handle the case where we just got created...
INITIALIZE OBJECT WITH original_object=SomeSpecialist.newItem(self.id)
# ...and the case where we already existed
WITH SomeSpecialist.getItem(self.id) COMPUTE original_object=RESULT
# Map the other object's names to mine...
WITH self.original_object COMPUTE my_foo=its_foo, my_bar=its_bar
# ...and mine to its
WHEN OBJECT ADDED,CHANGED STORE my_foo, my_bar USING
self.original_object.mange_changeProperties(
its_foo=self.my_foo, its_bar=self.my_bar
)
# Last, but not least, get rid of the other guy when I'm deleted
WHEN OBJECT DELETED CALL self.original_object.manage_delete()
- Complex storage mapping using a memento. This example
illustrates mapping a more complex kind of attribute into data
storage. 'DelKeywords' and 'AddKeywords' are methods (in the
SkinScript method's acquisition context) that delete or add
keyword records in some (unspecified) kind of database, given an
item id and a list of keywords. The example calls a method on the
DataSkin called 'getKeywords()' to extract keywords from the
'description' attribute. If the 'description' attribute is changed
during the (sub)transaction, at commit time the external database
will be updated. For illustrative purposes, appropriate WHEN/CALL
declarations are included to ensure that adds and deletes are
properly handled. Notice that the DELETED declaration uses the
value for the keywords which existed at the **start** of the
(sub)transaction, since it could theoretically have changed before
the object ended up being deleted::
WHEN OBJECT CHANGED STORE description USING
DelKeywords(item=self.id, kwlist=OLD['kw']),
AddKeywords(item=self.id, kwlist=self.getKeywords(self.description))
SAVING
kw=getKeywords(description)
WHEN OBJECT ADDED CALL
AddKeywords(item=self.id,kwlist=getKeywords(self.description))
WHEN OBJECT DELETED CALL
DelKeywords(item=self.id,kwlist=OLD['kw'])
SAVING
kw=getKeywords(description)
STORE *attributelist* IN SELF
What It's For
Specifying attributes to be stored persistently within the object.
How It Works
When an attribute listed in "attributelist" is changed/deleted, it
will be stored directly in the object as a persistent attribute.
Of course, this will only work if the object itself is stored in
the ZODB, and no previous declarations or AttributeProviders
declared storage for the attribute. "attributelist" can be just an
asterisk ('*') to indicate that all attributes not stored in some
other way should be stored persistently.
Examples
- Simple storage. The attributes 'foo', 'bar', and 'baz' will be
stored persistently in the ZODB. (Note that if the DataSkin is
stored in a Rack which is not using persistent storage, this will
not work, and data will silently be lost.)
STORE foo, bar, baz IN SELF
- Wildcard. Any attribute which is not explicitly claimed for
storage by another attribute provider/declaration will be stored
persistently, assuming that there is not another wildcard attribute
setter which appears earlier than this declaration in precedence
order.
STORE * IN SELF
WHEN *eventspec* CALL *expression* [ SAVING *mementolist* ]
What It's For
Calling an expression at (sub)transaction commit if any of the
specified events have occurred.
How It Works
At (sub)transaction commit time, if one of the events specified in
"eventspec" occurred, "expression" is called, with the 'OLD[]'
variable containing the values saved by "mementolist", if
specified.
Examples
- E-mail notification upon object add. 'EMailToManager' is a DTML
document or method in the acquisition context of the SkinScript
method, which expects a 'new_item' parameter containing the object
which the notification is supposed to be about. Note that if this
snippet were used in a Customizer, the 'ADD' event can occur when
an object is moved into the area covered by that Customizer. This
would mean notifications would be sent for objects which were not
necessarily newly created::
WHEN OBJECT ADDED CALL EMailToManager(_.None,_,new_item=self)
- Catalog example #1. This is an interesting way of doing
automatic cataloging/re-cataloging of objects without having them
be CatalogAware. Note that the order of declarations here is very
important: when an object is changed, 'uncatalog_object' will be
called before 'catalog_object'. 'SomeCatalog', of course, is a
catalog somewhere in the SkinScript method's acquisition context::
WHEN OBJECT CHANGED,DELETED CALL SomeCatalog.uncatalog_object(self.absolute_url(1))
WHEN OBJECT ADDED,CHANGED CALL SomeCatalog.catalog_object(self,self.absolute_url(1))
- Complex event alert. This example triggers if there is a 5% or
greater change in the ROI of an investment, based on any of the
input factors having changed (e.g. cashflow amounts or dates). It
works by saving the old result of the DataSkin's 'computeROI()'
method right before any change is made to the DataSkin's
attributes. The 'CALL' expression is called at (sub)transaction
commit if any changes have been made to the object. That
expression compares the new value of 'computeROI()' with the old
value as a percentage change, and if it's greater than 5%, calls
the 'SendROIAlert()' function which is somewhere in the SkinScript
method's acquisition context::
WHEN OBJECT CHANGED CALL
(abs(self.computeROI()-OLD['ROI'])/OLD['ROI'] > .05 )
and SendROIAlert(investment=self)
SAVING
ROI=computeROI()
- Catalog example #2. The previous catalog example recatalogs the
object if *any* attribute is changed, even if it is simply set back
to what it was in the first place. While future versions of
ZCatalog may optimize this case, we can do it ourselves with a bit
more complex SkinScript. This example will only recatalog the
object if attributes 'foo' or 'bar' were set or the result of
method 'baz' has changed::
WHEN OBJECT ADDED CALL SomeCatalog.catalog_object(self,self.absolute_url(1))
WHEN OBJECT DELETED CALL SomeCatalog.uncatalog_object(self.absolute_url(1))
WHEN OBJECT CHANGED CALL
(HAS_CHANGED('foo') or HAS_CHANGED('bar') or self.baz()<>OLD['baz']) and
( SomeCatalog.uncatalog_object(self.absolute_url(1)),
SomeCatalog.catalog_object(self,self.absolute_url(1))
)
SAVING baz=baz()
- Generic method-driven storage with custom method. In this
example, it is assumed that 'SomeMethod' is a DTML, SQL, or Python
method that expects to get a DataSkin and save its attributes in
an external database. The method must be smart enough to handle an
'ADD', 'CHANGE', or 'DELETE' event, and takes a parameter called
'what_happened' to tell it which one happened. In the case of an
SQL method, it's pretty straightforward to write DTML that
generates an appropriate 'INSERT', 'UPDATE', or 'DELETE' statement
according to which event happened. By using the 'HAS_CHANGED'
function, 'SomeMethod' can even generate an optimal 'UPDATE'
statement that only changes fields that have actually changed. As
you can see, this example puts the bulk of the processing burden on
'SomeMethod', which has to be written specifically for the
situation. This may be optimal, however, if you are going to have
to write the methods anyway::
WHEN OBJECT ADDED,CHANGED,DELETED CALL
SomeMethod(object=self, what_happened=TRIGGER_EVENT, has_changed=HAS_CHANGED)
Language Keywords
The following keywords are SkinScript reserved words and cannot be
used as attribute names or appear in expressions. If you must
access an attribute with one of these names in an expression, you
must use '_["name"]' syntax, as is sometimes used in DTML expressions
to access otherwise inaccessible names.
Keywords: 'ADDED CALL CHANGED COMPUTE DELETED DEPENDENT IN INCLUDE
INITIALIZE LET OBJECT ON OTHERWISE QUERY SAVING SELF SLOT STORE
USING WHEN WITH'
|