├── .editorconfig
├── .gitattributes
├── .scrutinizer.yml
├── .upgrade.yml
├── CHANGELOG.md
├── CODE-OF-CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── _config.php
├── composer.json
├── css
└── FrontEndGridField.css
├── images
├── arrows.png
├── btn-icon
│ ├── cross.png
│ ├── document--pencil.png
│ ├── ellipsis.png
│ └── magnifier.png
└── icons
│ ├── add.png
│ ├── chain--minus.png
│ ├── chain--plus.png
│ ├── cross-circle.png
│ ├── download-csv.png
│ ├── filter-icons.png
│ └── pagination-arrows.png
├── javascript
├── FrontEndGridField.js
├── GridField.js
├── boot.template.js
└── externals
│ ├── hafriedlander
│ └── jquery-entwine
│ │ └── jquery.entwine-dist.js
│ └── silverstripe
│ ├── lib.js
│ └── ssui.core.js
├── src
└── Forms
│ └── GridField
│ ├── GridField.php
│ ├── GridFieldConfig_Base.php
│ ├── GridFieldConfig_RecordEditor.php
│ ├── GridFieldConfig_RecordViewer.php
│ ├── GridFieldConfig_RelationEditor.php
│ ├── GridFieldDetailForm.php
│ └── GridFieldDetailForm_ItemRequest.php
└── templates
└── WebbuildersGroup
└── FrontEndGridField
└── Forms
└── GridField
├── GridFieldDetailForm.ss
└── GridField_deleted.ss
/.editorconfig:
--------------------------------------------------------------------------------
1 | # For more information about the properties used in this file,
2 | # please see the EditorConfig documentation:
3 | # http://editorconfig.org
4 |
5 | [*]
6 | charset = utf-8
7 | end_of_line = lf
8 | indent_size = 4
9 | indent_style = space
10 | insert_final_newline = true
11 | trim_trailing_whitespace = true
12 |
13 | [{*.yml,package.json}]
14 | indent_size = 2
15 |
16 | # The indent size used in the package.json file cannot be changed:
17 | # https://github.com/npm/npm/pull/3180#issuecomment-16336516
18 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | /.gitignore export-ignore
2 |
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | inherit: true
2 |
3 | checks:
4 | php:
5 | verify_property_names: true
6 | verify_argument_usable_as_reference: true
7 | verify_access_scope_valid: true
8 | useless_calls: true
9 | use_statement_alias_conflict: true
10 | variable_existence: true
11 | unused_variables: true
12 | unused_properties: true
13 | unused_parameters: true
14 | unused_methods: true
15 | unreachable_code: true
16 | too_many_arguments: true
17 | sql_injection_vulnerabilities: true
18 | simplify_boolean_return: true
19 | side_effects_or_types: true
20 | security_vulnerabilities: true
21 | return_doc_comments: true
22 | return_doc_comment_if_not_inferrable: true
23 | require_scope_for_properties: true
24 | require_scope_for_methods: true
25 | require_php_tag_first: true
26 | psr2_switch_declaration: true
27 | psr2_class_declaration: true
28 | property_assignments: true
29 | prefer_while_loop_over_for_loop: true
30 | precedence_mistakes: true
31 | precedence_in_conditions: true
32 | phpunit_assertions: true
33 | php5_style_constructor: true
34 | parse_doc_comments: true
35 | parameter_non_unique: true
36 | parameter_doc_comments: true
37 | param_doc_comment_if_not_inferrable: true
38 | optional_parameters_at_the_end: true
39 | one_class_per_file: true
40 | no_unnecessary_if: true
41 | no_trailing_whitespace: true
42 | no_property_on_interface: true
43 | no_non_implemented_abstract_methods: true
44 | no_error_suppression: true
45 | no_duplicate_arguments: true
46 | no_commented_out_code: true
47 | newline_at_end_of_file: true
48 | missing_arguments: true
49 | method_calls_on_non_object: true
50 | instanceof_class_exists: true
51 | foreach_traversable: true
52 | fix_line_ending: true
53 | fix_doc_comments: true
54 | duplication: true
55 | deprecated_code_usage: true
56 | deadlock_detection_in_loops: true
57 | code_rating: true
58 | closure_use_not_conflicting: true
59 | catch_class_exists: true
60 | blank_line_after_namespace_declaration: false
61 | avoid_multiple_statements_on_same_line: true
62 | avoid_duplicate_types: true
63 | avoid_conflicting_incrementers: true
64 | avoid_closing_tag: true
65 | assignment_of_null_return: true
66 | argument_type_checks: true
67 |
68 | filter:
69 | paths: [code/*, tests/*]
70 |
--------------------------------------------------------------------------------
/.upgrade.yml:
--------------------------------------------------------------------------------
1 | mappings:
2 | FrontEndGridField: WebbuildersGroup\FrontEndGridField\Forms\GridField\GridField
3 | FrontEndGridFieldConfig_Base: WebbuildersGroup\FrontEndGridField\Forms\GridField\GridFieldConfig_Base
4 | FrontEndGridFieldConfig_RecordViewer: WebbuildersGroup\FrontEndGridField\Forms\GridField\GridFieldConfig_RecordViewer
5 | FrontEndGridFieldConfig_RecordEditor: WebbuildersGroup\FrontEndGridField\Forms\GridField\GridFieldConfig_RecordEditor
6 | FrontEndGridFieldConfig_RelationEditor: WebbuildersGroup\FrontEndGridField\Forms\GridField\GridFieldConfig_RelationEditor
7 | FrontEndGridFieldDetailForm: WebbuildersGroup\FrontEndGridField\Forms\GridField\GridFieldDetailForm
8 | FrontEndGridFieldDetailForm_ItemRequest: WebbuildersGroup\FrontEndGridField\Forms\GridField\GridFieldDetailForm_ItemRequest
9 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## [2.1.0](https://github.com/webbuilders-group/silverstripe-frontendgridfield/tree/2.1.0) (2023-07-06)
4 | [Full Changelog](https://github.com/webbuilders-group/silverstripe-frontendgridfield/compare/2.0.6...2.1.0)
5 |
6 | ## [2.0.6](https://github.com/webbuilders-group/silverstripe-frontendgridfield/tree/2.0.6) (2021-06-22)
7 | [Full Changelog](https://github.com/webbuilders-group/silverstripe-frontendgridfield/compare/2.0.5...2.0.6)
8 |
9 | ## [2.0.5](https://github.com/webbuilders-group/silverstripe-frontendgridfield/tree/2.0.5) (2021-04-14)
10 | [Full Changelog](https://github.com/webbuilders-group/silverstripe-frontendgridfield/compare/2.0.4...2.0.5)
11 |
12 | ## [2.0.4](https://github.com/webbuilders-group/silverstripe-frontendgridfield/tree/2.0.4) (2021-01-06)
13 | [Full Changelog](https://github.com/webbuilders-group/silverstripe-frontendgridfield/compare/2.0.3...2.0.4)
14 |
15 | ## [2.0.3](https://github.com/webbuilders-group/silverstripe-frontendgridfield/tree/2.0.3) (2020-11-10)
16 | [Full Changelog](https://github.com/webbuilders-group/silverstripe-frontendgridfield/compare/2.0.2...2.0.3)
17 |
18 | ## [2.0.2](https://github.com/webbuilders-group/silverstripe-frontendgridfield/tree/2.0.2) (2020-02-07)
19 | [Full Changelog](https://github.com/webbuilders-group/silverstripe-frontendgridfield/compare/2.0.1...2.0.2)
20 |
21 | ## [2.0.1](https://github.com/webbuilders-group/silverstripe-frontendgridfield/tree/2.0.1) (2020-01-16)
22 | [Full Changelog](https://github.com/webbuilders-group/silverstripe-frontendgridfield/compare/2.0.0...2.0.1)
23 |
24 | ## [2.0.0](https://github.com/webbuilders-group/silverstripe-frontendgridfield/tree/2.0.0) (2019-10-02)
25 | [Full Changelog](https://github.com/webbuilders-group/silverstripe-frontendgridfield/compare/1.0.0...2.0.0)
26 |
27 | ## [1.0.0](https://github.com/webbuilders-group/silverstripe-frontendgridfield/tree/1.0.0) (2017-06-16)
28 | [Full Changelog](https://github.com/webbuilders-group/silverstripe-frontendgridfield/compare/0.3.0...1.0.0)
29 |
30 | ## [0.3.0](https://github.com/webbuilders-group/silverstripe-frontendgridfield/tree/0.3.0) (2017-03-21)
31 | [Full Changelog](https://github.com/webbuilders-group/silverstripe-frontendgridfield/compare/0.2.0...0.3.0)
32 |
33 | **Closed issues:**
34 |
35 | - Build new Tag from current Master [\#9](https://github.com/webbuilders-group/silverstripe-frontendgridfield/issues/9)
36 |
37 | **Merged pull requests:**
38 |
39 | - fixes for SS 3.5 \(possibly 3.2+?\) [\#15](https://github.com/webbuilders-group/silverstripe-frontendgridfield/pull/15) ([xini](https://github.com/xini))
40 | - Added standard Scrutinizer config [\#14](https://github.com/webbuilders-group/silverstripe-frontendgridfield/pull/14) ([helpfulrobot](https://github.com/helpfulrobot))
41 | - Added standard .gitattributes file [\#13](https://github.com/webbuilders-group/silverstripe-frontendgridfield/pull/13) ([helpfulrobot](https://github.com/helpfulrobot))
42 | - Added standard .editorconfig file [\#11](https://github.com/webbuilders-group/silverstripe-frontendgridfield/pull/11) ([helpfulrobot](https://github.com/helpfulrobot))
43 |
44 | ## [0.2.0](https://github.com/webbuilders-group/silverstripe-frontendgridfield/tree/0.2.0) (2014-12-13)
45 | [Full Changelog](https://github.com/webbuilders-group/silverstripe-frontendgridfield/compare/0.1.1...0.2.0)
46 |
47 | **Implemented enhancements:**
48 |
49 | - Unit Tests [\#1](https://github.com/webbuilders-group/silverstripe-frontendgridfield/issues/1)
50 |
51 | **Closed issues:**
52 |
53 | - Delete functions not working [\#4](https://github.com/webbuilders-group/silverstripe-frontendgridfield/issues/4)
54 |
55 | **Merged pull requests:**
56 |
57 | - Make sure jQuery is always included [\#7](https://github.com/webbuilders-group/silverstripe-frontendgridfield/pull/7) ([jedateach](https://github.com/jedateach))
58 | - Allow customising FrontEndGridField css with themed css [\#6](https://github.com/webbuilders-group/silverstripe-frontendgridfield/pull/6) ([jedateach](https://github.com/jedateach))
59 | - Added composer installer name [\#2](https://github.com/webbuilders-group/silverstripe-frontendgridfield/pull/2) ([jedateach](https://github.com/jedateach))
60 |
61 | ## [0.1.1](https://github.com/webbuilders-group/silverstripe-frontendgridfield/tree/0.1.1) (2014-01-03)
62 | [Full Changelog](https://github.com/webbuilders-group/silverstripe-frontendgridfield/compare/0.1...0.1.1)
63 |
64 | ## [0.1](https://github.com/webbuilders-group/silverstripe-frontendgridfield/tree/0.1) (2013-12-11)
65 |
--------------------------------------------------------------------------------
/CODE-OF-CONDUCT.md:
--------------------------------------------------------------------------------
1 | When having discussions about this module in issues or pull request please adhere to the [SilverStripe Community Code of Conduct](https://docs.silverstripe.org/en/contributing/code_of_conduct).
2 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributing
2 | =================
3 | ## Reporting an issue
4 | When you're reporting an issue please ensure you specify what version of SilverStripe you are using. Also be sure to include any JavaScript or PHP errors you receive, for PHP errors please ensure you include the full stack trace. Also please include your implementation code (where your setting up your grid field) as well as how you produced the issue. You may also be asked to provide some of the classes to aid in re-producing the issue. Stick with the issue, remember that you seen the issue not the maintainer of the module so it may take allot of questions to arrive at a fix or answer.
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2023, The Web Builders Group Inc.
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | Redistributions in binary form must reproduce the above copyright notice, this
11 | list of conditions and the following disclaimer in the documentation and/or
12 | other materials provided with the distribution.
13 |
14 | Neither the name of The Web Builders Group Inc nor the names of its
15 | contributors may be used to endorse or promote products derived from
16 | this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Front-End GridField
2 | =================
3 | Wraps gridfield adding support for using it on the front-end of a site.
4 |
5 | ## Maintainer Contact
6 | * Ed Chipman ([UndefinedOffset](https://github.com/UndefinedOffset))
7 |
8 | ## Requirements
9 | * SilverStripe Framework 4.3+|5.0+
10 |
11 |
12 | ## Installation
13 | * Download the module from here https://github.com/webbuilders-group/silverstripe-frontendgridfield/archive/master.zip
14 | * Extract the downloaded archive into your site root so that the destination folder is called frontendgridfield, opening the extracted folder should contain _config.php in the root along with other files/folders
15 | * Run dev/build?flush=all to regenerate the manifest
16 |
17 |
18 | ## Usage
19 | Instead of using the GridField class you need to use FrontEndGridField for use on the front-end, note it is not recommended to be used in the CMS. As well instead of using the GridFieldConfig extensions provided with SilverStripe use FrontEndGridFieldConfig_Base, FrontEndGridFieldConfig_RecordEditor, FrontEndGridFieldConfig_RecordViewer, or FrontEndGridFieldConfig_RelationEditor. If you are building your own GridField config ensure that you use FrontEndGridFieldDetailForm instead of GridFieldDetailForm.
20 |
--------------------------------------------------------------------------------
/_config.php:
--------------------------------------------------------------------------------
1 | div {
2 | margin-bottom: 36px;
3 | }
4 |
5 | form .ss-gridfield > div.addNewGridFieldButton {
6 | margin-bottom: 0;
7 | }
8 |
9 | form .ss-gridfield > div.addNewGridFieldButton .action {
10 | margin-bottom: 12px;
11 | }
12 |
13 | form .ss-gridfield > div.ss-gridfield-buttonrow-before {
14 | margin-bottom: 0;
15 | }
16 |
17 | form .ss-gridfield > div.ss-gridfield-buttonrow-before .action {
18 | display: inline-block;
19 | }
20 |
21 | form .ss-gridfield > div.ss-gridfield-buttonrow-before .action,
22 | form .ss-gridfield > div.ss-gridfield-buttonrow-before input {
23 | margin-bottom: 12px;
24 | }
25 |
26 | form .ss-gridfield > div.ss-gridfield-buttonrow-after {
27 | margin-bottom: 0;
28 | }
29 |
30 | form .ss-gridfield > div.ss-gridfield-buttonrow-after .action,
31 | form .ss-gridfield > div.ss-gridfield-buttonrow-after input {
32 | margin-top: 12px;
33 | }
34 |
35 | form .ss-gridfield[data-selectable] tr.ui-selected,
36 | form .ss-gridfield[data-selectable] tr.ui-selecting {
37 | background: #FFFAD6 !important;
38 | }
39 |
40 | form .ss-gridfield[data-selectable] td {
41 | cursor: pointer;
42 | }
43 |
44 | form .ss-gridfield span button.action_gridfield_relationfind {
45 | display: none;
46 | }
47 |
48 | form .ss-gridfield p button.action_export span.btn-icon-download-csv {
49 | height: 17px;
50 | }
51 |
52 | form .ss-gridfield .right {
53 | float: right;
54 | }
55 |
56 | form .ss-gridfield .right > * {
57 | float: right;
58 | margin-left: 8px;
59 | }
60 |
61 | form .ss-gridfield .pull-xs-right .pagination-records-number {
62 | font-size: 1.0em;
63 | padding: 6px 3px 6px 0;
64 | color: white;
65 | text-shadow: 0px -1px 0 rgba(0, 0, 0, 0.2);
66 | font-weight: normal;
67 | }
68 |
69 | form .ss-gridfield .pull-xs-left {
70 | float: left;
71 | }
72 |
73 | form .ss-gridfield .pull-xs-left > * {
74 | margin-right: 8px;
75 | float: left;
76 | }
77 |
78 | form .ss-gridfield {
79 | overflow-x: hidden;
80 | }
81 |
82 | form .ss-gridfield .grid-levelup {
83 | text-indent: -9999em;
84 | margin-bottom: 6px;
85 | }
86 |
87 | form .ss-gridfield .grid-levelup a.list-parent-link {
88 | background: transparent url(../images/gridfield-level-up.png) no-repeat 0 0;
89 | display: block;
90 | }
91 |
92 | form .ss-gridfield .add-existing-autocompleter span {
93 | float: left;
94 | display: inline-block;
95 | vertical-align: top;
96 | }
97 |
98 | form .ss-gridfield .add-existing-autocompleter input.relation-search {
99 | width: 270px;
100 | height: 32px;
101 | margin-bottom: 12px;
102 | border-top-right-radius: 0;
103 | border-bottom-right-radius: 0;
104 | }
105 |
106 | form .ss-gridfield .add-existing-autocompleter button.action_gridfield_relationadd {
107 | height: 32px;
108 | margin-left: 0;
109 | border-top-left-radius: 0;
110 | border-bottom-left-radius: 0;
111 | border-left: none;
112 | }
113 |
114 | form .ss-gridfield .grid-csv-button,
115 | form .ss-gridfield .grid-print-button {
116 | margin-bottom: 0;
117 | font-size: 12px;
118 | display: -moz-inline-stack;
119 | display: inline-block;
120 | vertical-align: middle;
121 | }
122 |
123 | form table.grid-field__table {
124 | display: table;
125 | -moz-box-shadow: none;
126 | -webkit-box-shadow: none;
127 | box-shadow: none;
128 | padding: 0;
129 | border-collapse: separate;
130 | border-bottom: 0 none;
131 | width: 100%;
132 | }
133 |
134 | form table.grid-field__table thead {
135 | color: #323e46;
136 | background: transparent;
137 | }
138 |
139 | form table.grid-field__table thead tr.grid-field__filter-header.grid-field__search-holder--hidden {
140 | display: none;
141 | }
142 |
143 | form table.grid-field__table thead tr.grid-field__filter-header .fieldgroup {
144 | position: relative;
145 |
146 | display: flex;
147 |
148 | align-items: stretch;
149 |
150 | max-width: 512px;
151 | }
152 |
153 | form table.grid-field__table thead tr.grid-field__filter-header .fieldgroup .fieldgroup-field {
154 | padding: 0;
155 |
156 | flex: 1 1 auto;
157 | }
158 |
159 | form table.grid-field__table thead tr.grid-field__filter-header .fieldgroup .fieldgroup-field > .action {
160 | position: static;
161 | }
162 |
163 | form table.grid-field__table thead tr.grid-field__filter-header .fieldgroup .fieldgroup-field.last {
164 | position: absolute;
165 | right: 0;
166 | top: 0;
167 |
168 | width: auto;
169 | height: 100%;
170 | }
171 |
172 | form table.grid-field__table thead tr.grid-field__filter-header th:last-child .fieldgroup .fieldgroup-field.last {
173 | position: static;
174 | top: auto;
175 | right: auto;
176 | }
177 |
178 | form table.grid-field__table thead tr.grid-field__filter-header button.btn.ss-gridfield-button-reset {
179 | display: block;
180 |
181 | position: absolute;
182 | top: 50%;
183 | right: 0;
184 |
185 | padding: 0;
186 |
187 | opacity: 0.5;
188 |
189 | transform: translateY(-50%);
190 |
191 | border-radius: 0;
192 | }
193 |
194 | form table.grid-field__table thead tr.grid-field__filter-header button.btn.ss-gridfield-button-reset:hover {
195 | opacity: 1;
196 | }
197 |
198 | form table.grid-field__table thead tr.grid-field__filter-header button.btn.ss-gridfield-button-reset::before {
199 | background: url(./../images/btn-icon/cross.png) no-repeat center center;
200 |
201 | display: block;
202 |
203 | width: 100%;
204 | height: 100%;
205 |
206 | content: "";
207 | }
208 |
209 | form table.grid-field__table thead tr:first-child th:first-child {
210 | -moz-border-radius-topleft: 5px;
211 | -webkit-border-top-left-radius: 5px;
212 | border-top-left-radius: 5px;
213 | }
214 |
215 | form table.grid-field__table thead tr:first-child th:last-child {
216 | -moz-border-radius-topright: 5px;
217 | -webkit-border-top-right-radius: 5px;
218 | border-top-right-radius: 5px;
219 | }
220 |
221 | form table.grid-field__table tbody {
222 | background: #FFF;
223 | }
224 |
225 | form table.grid-field__table tbody tr {
226 | cursor: pointer;
227 | }
228 |
229 | form table.grid-field__table tbody td {
230 | width: auto;
231 | max-width: 500px;
232 | word-wrap: break-word;
233 | }
234 |
235 | form table.grid-field__table tbody td.col-buttons {
236 | width: 1px;
237 | padding: 0 8px;
238 | text-align: right;
239 | white-space: nowrap;
240 | vertical-align: middle;
241 | }
242 |
243 | form table.grid-field__table tbody td.col-listChildrenLink {
244 | width: 16px;
245 | border-right: none;
246 | text-indent: -9999em;
247 | padding: 0;
248 | }
249 |
250 | form table.grid-field__table tbody td.col-listChildrenLink .list-children-link {
251 | background: transparent url(../images/sitetree_ss_default_icons.png) no-repeat 3px -4px;
252 | display: block;
253 | }
254 |
255 | form table.grid-field__table tbody td.col-getTreeTitle span.item {
256 | color: #0071c4;
257 | }
258 |
259 | form table.grid-field__table tbody td.col-getTreeTitle span.badge {
260 | clear: both;
261 | text-transform: uppercase;
262 | display: inline-block;
263 | padding: 0px 3px;
264 | font-size: 0.75em;
265 | line-height: 1em;
266 | margin-left: 10px;
267 | margin-right: 6px;
268 | margin-top: -1px;
269 | -moz-border-radius: 2px/2px;
270 | -webkit-border-radius: 2px 2px;
271 | border-radius: 2px/2px;
272 | }
273 |
274 | form table.grid-field__table tbody td.col-getTreeTitle span.badge.status-modified {
275 | color: #7E7470;
276 | border: 1px solid #C9B800;
277 | background-color: #FFF0BC;
278 | }
279 |
280 | form table.grid-field__table tbody td.col-getTreeTitle span.badge.status-addedtodraft {
281 | color: #7E7470;
282 | border: 1px solid #C9B800;
283 | background-color: #FFF0BC;
284 | }
285 |
286 | form table.grid-field__table tbody td.col-getTreeTitle span.badge.status-deletedonlive {
287 | color: #636363;
288 | border: 1px solid #E49393;
289 | background-color: #F2DADB;
290 | }
291 |
292 | form table.grid-field__table tbody td.col-getTreeTitle span.badge.status-removedfromdraft {
293 | color: #636363;
294 | border: 1px solid #E49393;
295 | background-color: #F2DADB;
296 | }
297 |
298 | form table.grid-field__table tbody td.col-getTreeTitle span.badge.status-workflow-approval {
299 | color: #56660C;
300 | border: 1px solid #7C8816;
301 | background-color: #DAE79A;
302 | }
303 |
304 | form table.grid-field__table tbody td button {
305 | border: none;
306 | background: none;
307 | padding: 1px 0;
308 | width: auto;
309 | text-shadow: none;
310 | }
311 |
312 | form table.grid-field__table tbody td button.ui-state-hover {
313 | background: none;
314 | -moz-box-shadow: none;
315 | -webkit-box-shadow: none;
316 | box-shadow: none;
317 | }
318 |
319 | form table.grid-field__table tbody td button.ui-state-active {
320 | border: none;
321 | -moz-box-shadow: none;
322 | -webkit-box-shadow: none;
323 | box-shadow: none;
324 | }
325 |
326 | form table.grid-field__table tbody td button.gridfield-button-delete {
327 | width: 20px;
328 | margin: 0;
329 | }
330 |
331 | form table.grid-field__table tbody td button.gridfield-button-delete span.btn-icon-decline {
332 | left: 2px;
333 | }
334 |
335 | form table.grid-field__table tbody td a.view-link,
336 | form table.grid-field__table tbody td a.edit-link {
337 | position: relative;
338 | }
339 |
340 | form table.grid-field__table tbody td a.view-link::before,
341 | form table.grid-field__table tbody td a.edit-link::before,
342 | form table.grid-field__table .action--delete::before {
343 | display: inline-block;
344 |
345 | width: 16px;
346 | height: 16px;
347 |
348 | content: "";
349 |
350 | margin-right: 0.5em;
351 |
352 | vertical-align: middle;
353 | }
354 |
355 | form table.grid-field__table tbody td a.view-link::before {
356 | background: url(../images/btn-icon/magnifier.png) no-repeat 0 1px;
357 | }
358 |
359 | form table.grid-field__table tbody td a.edit-link::before {
360 | background: url(../images/btn-icon/document--pencil.png) no-repeat 2px 0px;
361 | }
362 |
363 | form table.grid-field__table .action--delete::before {
364 | background: url(./../images/icons/cross-circle.png) no-repeat;
365 | }
366 |
367 | form table.grid-field__table tfoot {
368 | color: #323e46;
369 | }
370 |
371 | form table.grid-field__table tfoot tr td {
372 | background: #b0bec7;
373 | padding: .7em;
374 | border-bottom: 1px solid rgba(0, 0, 0, 0.1);
375 | }
376 |
377 | form table.grid-field__table tr.grid-field__title-row th {
378 | position: relative;
379 | background: #98aab6;
380 | border-bottom: 1px solid #899eab;
381 | padding: 5px;
382 | min-height: 40px;
383 | background-image:
384 | url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2IwYmVjNyIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzk4YWFiNiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA==');
385 | background-size: 100%;
386 | background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #b0bec7),
387 | color-stop(100%, #98aab6));
388 | background-image: -moz-linear-gradient(#b0bec7, #98aab6);
389 | background-image: -webkit-linear-gradient(#b0bec7, #98aab6);
390 | background-image: linear-gradient(#b0bec7, #98aab6);
391 | text-shadow: 0px -1px 0 rgba(0, 0, 0, 0.4);
392 | }
393 |
394 | form table.grid-field__table tr.grid-field__title-row th h2 {
395 | padding: 0px;
396 | font-size: 16.8px;
397 | color: #fff;
398 | margin: 1px 8px 0;
399 | display: inline-block;
400 | float: left;
401 | }
402 |
403 | form table.grid-field__table tr.sortable-header {
404 | background: #dbe3e8;
405 | }
406 |
407 | form table.grid-field__table tr.sortable-header th {
408 | padding: 0;
409 | font-weight: normal;
410 | }
411 |
412 | form table.grid-field__table tr.sortable-header th .btn {
413 | font-weight: normal;
414 | }
415 |
416 | form table.grid-field__table tr:hover {
417 | background: #FFFAD6;
418 | }
419 |
420 | form table.grid-field__table tr:first-child {
421 | background: transparent;
422 | }
423 |
424 | form table.grid-field__table tbody tr:first-child:hover {
425 | background: #FFFAD6;
426 | }
427 |
428 | form table.grid-field__table tr.ss-gridfield-even {
429 | background: #F0F4F7;
430 | }
431 |
432 | form table.grid-field__table tr.ss-gridfield-even.ss-gridfield-last {
433 | border-bottom: none;
434 | }
435 |
436 | form table.grid-field__table tr.ss-gridfield-even:hover {
437 | background: #FFFAD6;
438 | }
439 |
440 | form table.grid-field__table tr.even {
441 | background: #F0F4F7;
442 | }
443 |
444 | form table.grid-field__table tr.even:hover {
445 | background: #FFFAD6;
446 | }
447 |
448 | form table.grid-field__table tr th {
449 | font-weight: bold;
450 | font-size: 12px;
451 | color: #FFF;
452 | padding: 5px;
453 | border-right: 1px solid rgba(0, 0, 0, 0.1);
454 | text-align: left;
455 | }
456 |
457 | form table.grid-field__table tr th div.fieldgroup,
458 | form table.grid-field__table tr th div.fieldgroup-field {
459 | width: 100%;
460 | position: relative;
461 | }
462 |
463 | form table.grid-field__table tr th div.fieldgroup {
464 | padding-right: 0;
465 | }
466 |
467 | form table.grid-field__table tr th div.fieldgroup.filter-buttons {
468 | min-width: 49px;
469 | box-shadow: none;
470 | border: none;
471 | }
472 |
473 | form table.grid-field__table tr th div.fieldgroup.filter-buttons div {
474 | width: auto;
475 | display: inline;
476 | }
477 |
478 | form table.grid-field__table tr th.main {
479 | white-space: nowrap;
480 | border-top: 1px solid #a4b4bf;
481 | border-left: 1px solid #a4b4bf;
482 | color: #fff;
483 | background: #98aab6;
484 | border-bottom: 1px solid rgba(0, 0, 0, 0.1);
485 | }
486 |
487 | form table.grid-field__table tr th.main span {
488 | text-shadow: 0px -1px 0 rgba(0, 0, 0, 0.2);
489 | padding-left: 8px;
490 | padding-right: 8px;
491 | overflow: hidden;
492 | white-space: nowrap;
493 | text-overflow: ellipsis;
494 | -o-text-overflow: ellipsis;
495 | margin-right: 8px;
496 | }
497 |
498 | form table.grid-field__table tr th.main.col-listChildrenLink {
499 | border-right: none;
500 | }
501 |
502 | form table.grid-field__table tr th.extra,
503 | form table.grid-field__table tr th.action {
504 | padding: 0;
505 | cursor: default;
506 | }
507 |
508 | form table.grid-field__table tr th.extra {
509 | position: relative;
510 | background: #637276;
511 | background: rgba(0, 0, 0, 0.7);
512 | padding: 5px;
513 | border-top: rgba(0, 0, 0, 0.2);
514 | }
515 |
516 | form table.grid-field__table tr th.extra input {
517 | height: 28px;
518 | }
519 |
520 | form table.grid-field__table tr th.extra button.btn {
521 | padding: .3em;
522 | line-height: 1;
523 | -moz-box-shadow: none;
524 | -webkit-box-shadow: none;
525 | box-shadow: none;
526 | position: relative;
527 | border-bottom-width: 0;
528 | -moz-border-radius: 2px/2px;
529 | -webkit-border-radius: 2px 2px;
530 | border-radius: 2px/2px;
531 | }
532 |
533 | form table.grid-field__table tr th.extra select {
534 | margin: 0;
535 | }
536 |
537 | form table.grid-field__table tr th button.action_gridfield_relationadd:hover {
538 | color: #444 !important;
539 | /* Not sure why IE think it needs this */
540 | }
541 |
542 | form table.grid-field__table tr th button:hover {
543 | color: #ccc !important;
544 | /* Not sure why IE think it needs this */
545 | }
546 |
547 | form table.grid-field__table tr th button.grid-field__sort:hover {
548 | color: #fff !important;
549 | -moz-box-shadow: none;
550 | -webkit-box-shadow: none;
551 | box-shadow: none;
552 | }
553 |
554 | form table.grid-field__table tr th button.grid-field__sort {
555 | background: transparent url(../images/arrows.png) no-repeat right 6px;
556 | border: none;
557 | width: 100%;
558 | text-align: left;
559 | padding: 2px 8px 2px 0;
560 | text-shadow: 0px -1px 0 rgba(0, 0, 0, 0.2);
561 | color: #fff;
562 | -moz-border-radius: 0;
563 | -webkit-border-radius: 0;
564 | border-radius: 0;
565 | }
566 |
567 | form table.grid-field__table tr th button.grid-field__sort:hover {
568 | background-position: right -34px;
569 | }
570 |
571 | form table.grid-field__table tr th button.grid-field__sort.ss-gridfield-sorted-desc,
572 | form table.grid-field__table tr th button.grid-field__sort.grid-field__sorted-desc {
573 | background-position: right -72px;
574 | }
575 |
576 | form table.grid-field__table tr th button.grid-field__sort.ss-gridfield-sorted-asc,
577 | form table.grid-field__table tr th button.grid-field__sort.grid-field__sorted-asc {
578 | background-position: right -116px;
579 | }
580 |
581 | form table.grid-field__table tr th button.btn.font-icon-search {
582 | background-color: #55a4d2;
583 | border: none;
584 | display: block;
585 | text-indent: -9999em;
586 | width: 30px;
587 | height: 25px;
588 | background: url(../images/icons/filter-icons.png) no-repeat -15px 4px,
589 | url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzMzOGRjMSIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzI4NzA5OSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA==');
590 | background: url(../images/icons/filter-icons.png) no-repeat -15px 4px,
591 | -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #338dc1),
592 | color-stop(100%, #287099));
593 | background: url(../images/icons/filter-icons.png) no-repeat -15px 4px,
594 | -moz-linear-gradient(#338dc1, #287099);
595 | background: url(../images/icons/filter-icons.png) no-repeat -15px 4px,
596 | -webkit-linear-gradient(#338dc1, #287099);
597 | background: url(../images/icons/filter-icons.png) no-repeat -15px 4px,
598 | linear-gradient(#338dc1, #287099);
599 | width: 26px;
600 | border-top: 1px solid #4199cd;
601 | }
602 |
603 | form table.grid-field__table tr th button.btn.font-icon-search.hover-alike:active,
604 | form table.grid-field__table tr th button.btn.font-icon-search:active,
605 | form table.grid-field__table tr th button.btn.font-icon-search.hover-alike,
606 | form table.grid-field__table tr th button.btn.font-icon-search:hover {
607 | background: url(../images/icons/filter-icons.png) no-repeat -15px 4px,
608 | url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzU1YTRkMiIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzMzOGRjMSIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA==');
609 | background: url(../images/icons/filter-icons.png) no-repeat -15px 4px,
610 | -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #55a4d2),
611 | color-stop(100%, #338dc1));
612 | background: url(../images/icons/filter-icons.png) no-repeat -15px 4px,
613 | -moz-linear-gradient(#55a4d2, #338dc1);
614 | background: url(../images/icons/filter-icons.png) no-repeat -15px 4px,
615 | -webkit-linear-gradient(#55a4d2, #338dc1);
616 | background: url(../images/icons/filter-icons.png) no-repeat -15px 4px,
617 | linear-gradient(#55a4d2, #338dc1);
618 | }
619 |
620 | form table.grid-field__table tr th button.btn.font-icon-search.grid-field__filter-open {
621 | margin-left: 12px;
622 | border: none;
623 | background: url(../images/icons/filter-icons.png) no-repeat -17px 6px;
624 | padding-right: 46px;
625 | margin: 0 6px;
626 | }
627 |
628 | form table.grid-field__table tr th button.btn.font-icon-search.grid-field__filter-open span {
629 | opacity: 0.4;
630 | position: absolute;
631 | width: 10px;
632 | left: 30px;
633 | top: 40%;
634 | background: url(../admin/images/btn_arrow_down_grey.png) no-repeat 0px 0px;
635 | }
636 |
637 | form table.grid-field__table tr th button.btn.font-icon-search.grid-field__filter-open:hover {
638 | background: url(../images/icons/filter-icons.png) no-repeat -17px -38px;
639 | -moz-box-shadow: none;
640 | -webkit-box-shadow: none;
641 | box-shadow: none;
642 | }
643 |
644 | form table.grid-field__table tr th button.btn.font-icon-search.grid-field__filter-open:hover span {
645 | opacity: 0.9;
646 | }
647 |
648 | form table.grid-field__table tr th button.btn.ss-gridfield-button-close {
649 | background: url(../images/icons/filter-icons.png) no-repeat 8px -17px;
650 | border: none;
651 | display: block;
652 | text-indent: -9999em;
653 | width: 30px;
654 | height: 25px;
655 | width: 25px;
656 | opacity: 0.8;
657 | margin-right: -5px;
658 | }
659 |
660 | form table.grid-field__table tr th button.btn.ss-gridfield-button-close.hover-alike:active,
661 | form table.grid-field__table tr th button.btn.ss-gridfield-button-close:active,
662 | form table.grid-field__table tr th button.btn.ss-gridfield-button-close.hover-alike,
663 | form table.grid-field__table tr th button.btn.ss-gridfield-button-close:hover {
664 | opacity: 1;
665 | background: url(../images/icons/filter-icons.png) no-repeat 8px -17px,
666 | url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIgc3RvcC1vcGFjaXR5PSIwLjEiLz48c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiIHN0b3Atb3BhY2l0eT0iMC4xIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0idXJsKCNncmFkKSIgLz48L3N2Zz4g');
667 | background: url(../images/icons/filter-icons.png) no-repeat 8px -17px,
668 | -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, rgba(255, 255,
669 | 255, 0.1)), color-stop(100%, rgba(255, 255, 255, 0.1)));
670 | background: url(../images/icons/filter-icons.png) no-repeat 8px -17px,
671 | -moz-linear-gradient(rgba(255, 255, 255, 0.1),
672 | rgba(255, 255, 255, 0.1));
673 | background: url(../images/icons/filter-icons.png) no-repeat 8px -17px,
674 | -webkit-linear-gradient(rgba(255, 255, 255, 0.1),
675 | rgba(255, 255, 255, 0.1));
676 | background: url(../images/icons/filter-icons.png) no-repeat 8px -17px,
677 | linear-gradient(rgba(255, 255, 255, 0.1),
678 | rgba(255, 255, 255, 0.1));
679 | }
680 |
681 | form table.grid-field__table tr th button.btn.ss-gridfield-button-reset {
682 | border: none;
683 | display: block;
684 | text-indent: -9999em;
685 | width: 30px;
686 | height: 25px;
687 | position: absolute;
688 | top: -21px;
689 | right: -1px;
690 | width: 20px;
691 | height: 20px;
692 | display: none;
693 | }
694 |
695 | form table.grid-field__table tr th button.btn.ss-gridfield-button-reset.filtered {
696 | display: block;
697 | background: url(../admin/images/btn-icon/cross.png) no-repeat 0px 0px;
698 | opacity: 0.5;
699 | }
700 |
701 | form table.grid-field__table tr th button.btn.ss-gridfield-button-reset.filtered:hover {
702 | opacity: 0.8;
703 | }
704 |
705 | form table.grid-field__table tr th button.btn.ss-gridfield-button-reset.filtered:active {
706 | opacity: 1;
707 | }
708 |
709 | form table.grid-field__table tr th input.grid-field__sort {
710 | height: 25px;
711 | padding: 4px;
712 | border: 1px solid #313232;
713 | }
714 |
715 | form table.grid-field__table tr th input.grid-field__sort::-webkit-input-placeholder {
716 | font-style: italic;
717 | color: #ced5d7;
718 | }
719 |
720 | form table.grid-field__table tr th input.grid-field__sort:-moz-placeholder {
721 | font-style: italic;
722 | color: #ced5d7;
723 | }
724 |
725 | form table.grid-field__table tr th input.grid-field__sort:-ms-input-placeholder {
726 | font-style: italic;
727 | color: #ced5d7;
728 | }
729 |
730 | form table.grid-field__table tr th input.grid-field__sort:placeholder {
731 | font-style: italic;
732 | color: #ced5d7;
733 | }
734 |
735 | form table.grid-field__table tr th input.grid-field__sort:focus {
736 | -moz-box-shadow: none;
737 | -webkit-box-shadow: none;
738 | box-shadow: none;
739 | }
740 |
741 | form table.grid-field__table tr th span.non-sortable {
742 | display: block;
743 | padding: 6px 8px;
744 | }
745 |
746 | form table.grid-field__table tr td {
747 | border-right: 1px solid rgba(0, 0, 0, 0.1);
748 | padding: 8px 8px;
749 | color: #666;
750 | }
751 |
752 | form table.grid-field__table tr td.bottom-all {
753 | -moz-border-radius-bottomleft: 5px;
754 | -webkit-border-bottom-left-radius: 5px;
755 | border-bottom-left-radius: 5px;
756 | -moz-border-radius-bottomright: 5px;
757 | -webkit-border-bottom-right-radius: 5px;
758 | border-bottom-right-radius: 5px;
759 | background-image:
760 | url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4gPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJncmFkIiBncmFkaWVudFVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgeDE9IjAuNSIgeTE9IjAuMCIgeDI9IjAuNSIgeTI9IjEuMCI+PHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2IwYmVjNyIvPjxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iIzk4YWFiNiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAiIHk9IjAiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InVybCgjZ3JhZCkiIC8+PC9zdmc+IA==');
761 | background-size: 100%;
762 | background-image: -webkit-gradient(linear, 50% 0%, 50% 100%, color-stop(0%, #b0bec7),
763 | color-stop(100%, #98aab6));
764 | background-image: -moz-linear-gradient(#b0bec7, #98aab6);
765 | background-image: -webkit-linear-gradient(#b0bec7, #98aab6);
766 | background-image: linear-gradient(#b0bec7, #98aab6);
767 | padding: 4px 12px;
768 | }
769 |
770 | form table.grid-field__table tr td.bottom-all .datagrid-footer-message {
771 | text-align: center;
772 | padding-top: 6px;
773 | color: white;
774 | }
775 |
776 | form table.grid-field__table tr td.bottom-all .datagrid-pagination {
777 | padding-top: 1px;
778 | position: absolute;
779 | left: 50%;
780 | margin-left: -116px;
781 | z-index: 5;
782 | }
783 |
784 | form table.grid-field__table tr td.bottom-all .datagrid-pagination .pagination-page-number {
785 | color: white;
786 | text-align: center;
787 | text-shadow: 0px -1px 0 rgba(0, 0, 0, 0.2);
788 | }
789 |
790 | form table.grid-field__table tr td.bottom-all .datagrid-pagination .pagination-page-number input {
791 | width: 35px;
792 | height: 18px;
793 | margin-bottom: -6px;
794 | padding: 0px;
795 | border: 1px solid #899eab;
796 | border-bottom: 1px solid #a7b7c1;
797 | }
798 |
799 | form table.grid-field__table tr td.bottom-all .datagrid-pagination button {
800 | -moz-box-shadow: none;
801 | -webkit-box-shadow: none;
802 | box-shadow: none;
803 | border: none;
804 | width: 10px;
805 | margin: 0 10px;
806 | display: inline;
807 | float: none;
808 | }
809 |
810 | form table.grid-field__table tr td.bottom-all .datagrid-pagination button span {
811 | text-indent: -9999em;
812 | }
813 |
814 | form table.grid-field__table tr td.bottom-all .datagrid-pagination button.ss-gridfield-previouspage {
815 | background: url(../images/icons/pagination-arrows.png) no-repeat -23px 8px;
816 | }
817 |
818 | form table.grid-field__table tr td.bottom-all .datagrid-pagination button.ss-gridfield-nextpage {
819 | background: url(../images/icons/pagination-arrows.png) no-repeat -47px 8px;
820 | }
821 |
822 | form table.grid-field__table tr td.bottom-all .datagrid-pagination button.ss-gridfield-firstpage {
823 | background: url(../images/icons/pagination-arrows.png) no-repeat 0px 8px;
824 | }
825 |
826 | form table.grid-field__table tr td.bottom-all .datagrid-pagination button.ss-gridfield-lastpage {
827 | background: url(../images/icons/pagination-arrows.png) no-repeat -73px 8px;
828 | }
829 |
830 | form table.grid-field__table tr td.bottom-all .datagrid-pagination button.ssui-button-disabled {
831 | z-index: -1;
832 | }
833 |
834 | form table.grid-field__table tr td.bottom-all .pagination-records-number {
835 | float: right;
836 | padding: 6px 0;
837 | color: white;
838 | text-shadow: 0px -1px 0 rgba(0, 0, 0, 0.2);
839 | }
840 |
841 | form table.grid-field__table tr.last td {
842 | border-bottom: 0 none;
843 | }
844 |
845 | form table.grid-field__table td:first-child {
846 | border-left: 1px solid rgba(0, 0, 0, 0.1);
847 | }
848 |
849 | form table.grid-field__table td:last-child {
850 | border-right: 1px solid rgba(0, 0, 0, 0.1);
851 | }
852 |
853 | form table.grid-field__table td.col-StripThumbnail {
854 | padding: 2px 4px;
855 | width: 32px;
856 | height: 32px;
857 | }
858 |
859 | form table.grid-field__table td.col-StripThumbnail img {
860 | width: 32px;
861 | height: 32px;
862 | display: block;
863 | }
864 |
865 | form .ss-gridfield .btn, form .ss-gridfield .action {
866 | position: relative;
867 | }
868 |
869 | form .ss-gridfield th.extra .action {
870 | border: 0;
871 | }
872 |
873 | form .ss-gridfield .ui-button-text {
874 | display: block;
875 |
876 | padding: .4em 1em .4em 2.1em;
877 |
878 | line-height: 1.4;
879 | }
880 |
881 | form .ss-gridfield th.extra .ui-button-text {
882 | display: inline-block;
883 |
884 | padding: 0;
885 |
886 | line-height: normal;
887 | }
888 |
889 | form .ss-gridfield .btn.font-icon-plus-circled::before,
890 | form .ss-gridfield .action.action_gridfield_relationadd::before,
891 | form .ss-gridfield .font-icon-link-broken::before,
892 | form .ss-gridfield-buttonrow .action_export::before {
893 | display: inline-block;
894 |
895 | position: static;
896 | top: auto;
897 | left: auto;
898 |
899 | content: "";
900 |
901 | width: 16px;
902 | height: 16px;
903 |
904 | vertical-align: middle;
905 | }
906 |
907 | form .ss-gridfield .btn.font-icon-plus-circled::before {
908 | background: url(./../images/icons/add.png) no-repeat;
909 | }
910 |
911 | form .ss-gridfield .action.action_gridfield_relationadd::before {
912 | background: url(./../images/icons/chain--plus.png) no-repeat;
913 | }
914 |
915 | form .ss-gridfield .font-icon-link-broken::before {
916 | background: url(./../images/icons/chain--minus.png) no-repeat;
917 | }
918 |
919 | form .ss-gridfield-buttonrow .action_export::before {
920 | background: url(./../images/icons/download-csv.png) no-repeat;
921 | }
922 |
923 | form table.grid-field__table tr th input.grid-field__sort-field {
924 | width: 100%;
925 | height: auto;
926 | margin: 0;
927 | outline: none;
928 | border-radius: 4px;
929 | line-height: 16px;
930 | box-sizing: border-box;
931 | }
932 |
933 | form table.grid-field__table thead tr.grid-field__filter-header .fieldgroup .fieldgroup-field {
934 | float: left;
935 | }
936 |
937 | form table.grid-field__table td.ss-gridfield-item.loading {
938 | position: relative;
939 |
940 | height: 100px;
941 | }
942 |
943 | form .grid-field .pull-xs-right > * {
944 | float: right;
945 | margin-left: .6154rem;
946 | }
947 |
948 | form .grid-field .dropdown, form .grid-field .dropleft,
949 | form .grid-field .dropright, form .grid-field .dropup {
950 | position: relative;
951 | }
952 |
953 | form .grid-field .dropdown-menu {
954 | position: absolute;
955 | top: 100%;
956 | left: 0;
957 | z-index: 1000;
958 | display: none;
959 | float: left;
960 | min-width: 10rem;
961 | padding: .5rem 0;
962 | margin: .125rem 0 0;
963 | font-size: 1rem;
964 | color: #43536d;
965 | text-align: left;
966 | list-style: none;
967 | background-color: #fff;
968 | -webkit-background-clip: padding-box;
969 | background-clip: padding-box;
970 | border: 1px solid rgba(0,0,0,.15);
971 | border-radius: .23rem;
972 | -webkit-box-shadow: 0 .5rem 1rem rgba(0,0,0,.175);
973 | box-shadow: 0 .5rem 1rem rgba(0,0,0,.175);
974 |
975 | text-align: left;
976 | }
977 |
978 | form .grid-field .dropdown-menu.show {
979 | display: block;
980 | }
981 |
982 | form .grid-field .dropdown-menu-right {
983 | right: 0;
984 | left: auto;
985 | }
986 |
987 | form .grid-field .dropdown-menu[x-placement^="bottom"], form .grid-field .dropdown-menu[x-placement^="left"],
988 | form .grid-field .dropdown-menu[x-placement^="right"], form .grid-field .dropdown-menu[x-placement^="top"] {
989 | right: auto;
990 | bottom: auto;
991 | }
992 |
993 | form .grid-field .action-menu__dropdown .dropdown-item {
994 | cursor: pointer;
995 | }
996 |
997 | form .grid-field .dropdown-item {
998 | display: block;
999 | width: 100%;
1000 | margin: 0;
1001 | padding: .45rem 1.3rem;
1002 | clear: both;
1003 | font-weight: 400;
1004 | color: #43536d;
1005 | text-align: left !important;
1006 | white-space: nowrap;
1007 | background-color: transparent;
1008 | border: 0;
1009 | box-sizing: border-box;
1010 | }
1011 |
1012 | form .grid-field .dropdown-item:focus, form .grid-field .dropdown-item:hover {
1013 | color: #43536d;
1014 | text-decoration: none;
1015 | background-color: #eef0f4;
1016 | }
1017 |
1018 | form .grid-field .gridfield-actionmenu__container .action-menu__dropdown {
1019 | left: auto !important;
1020 | right: auto;
1021 | }
1022 |
1023 | form .grid-field .grid-field__col-compact > * {
1024 | vertical-align: middle;
1025 | margin-right: 6px;
1026 | }
1027 |
1028 | form .grid-field .gridfield-actionmenu__container {
1029 | display: -webkit-inline-box;
1030 | display: -webkit-inline-flex;
1031 | display: inline-flex;
1032 | margin: -.5em 0;
1033 | }
1034 |
1035 | form .grid-field .grid-field__col-compact > :last-child {
1036 | margin-right: 0;
1037 | }
1038 |
1039 | form .grid-field .action-menu__toggle {
1040 | width: 1.4em;
1041 | }
1042 |
1043 | form .grid-field .action-menu__toggle::before {
1044 | background: url(../images/btn-icon/ellipsis.png) no-repeat center center;
1045 |
1046 | display: block;
1047 |
1048 | width: 16px;
1049 | height: 16px;
1050 |
1051 | content: "";
1052 |
1053 | margin: 0 auto;
1054 | }
1055 |
1056 | form .grid-field .action-menu__toggle .sr-only {
1057 | display: none;
1058 | }
1059 |
1060 | form .grid-field span button.action.action_gridfield_relationfind {
1061 | display: none;
1062 | }
1063 |
1064 | form .grid-field .cms-content-loading-spinner {
1065 | position: absolute;
1066 | top: 0;
1067 | left: 0;
1068 | width: 100%;
1069 | height: 100%;
1070 | z-index: 9999;
1071 | display: -webkit-box;
1072 | display: -webkit-flex;
1073 | display: flex;
1074 | -webkit-box-align: center;
1075 | -webkit-align-items: center;
1076 | align-items: center;
1077 | -webkit-box-pack: center;
1078 | -webkit-justify-content: center;
1079 | justify-content: center
1080 | }
1081 |
1082 | form .grid-field .cms-content-loading-spinner .spinner {
1083 | width: 3rem;
1084 | height: 3rem;
1085 | padding: .5rem;
1086 | background-color: #fff;
1087 | border-radius: .192rem
1088 | }
1089 |
1090 | form .grid-field .cms-content-loading-spinner .spinner__animation {
1091 | width: 100%;
1092 | height: 100%
1093 | }
1094 |
1095 | form .grid-field .cms-content-loading-spinner .spinner__animation__empty {
1096 | fill: #8f9fba
1097 | }
1098 |
1099 | form .grid-field .cms-content-loading-spinner .spinner__animation__fill {
1100 | fill: none;
1101 | stroke: #005a93;
1102 | stroke-width: 5;
1103 | stroke-dasharray: 70;
1104 | -webkit-transform: translateZ(0);
1105 | transform: translateZ(0);
1106 | -webkit-animation: frontgridfield__spinner__animation__keyframes 1.5s infinite cubic-bezier(.445, .05, .55, .95) forwards;
1107 | -o-animation: frontgridfield__spinner__animation__keyframes 1.5s infinite cubic-bezier(.445, .05, .55, .95) forwards;
1108 | animation: frontgridfield__spinner__animation__keyframes 1.5s infinite cubic-bezier(.445, .05, .55, .95) forwards
1109 | }
1110 |
1111 | @media (-ms-high-contrast:none) {
1112 | form .grid-field .cms-content-loading-spinner .spinner__animation__fill {
1113 | -webkit-animation: frontgridfield__spinner__animation__keyframes_ie .5s alternate infinite cubic-bezier(.445, .05, .55, .95) forwards;
1114 | -o-animation: frontgridfield__spinner__animation__keyframes_ie .5s alternate infinite cubic-bezier(.445, .05, .55, .95) forwards;
1115 | animation: frontgridfield__spinner__animation__keyframes_ie .5s alternate infinite cubic-bezier(.445, .05, .55, .95) forwards
1116 | }
1117 | }
1118 |
1119 | @-webkit-keyframes frontgridfield__spinner__animation__keyframes {
1120 | 0% {
1121 | stroke-dashoffset: 140
1122 | }
1123 |
1124 | to {
1125 | stroke-dashoffset: 0
1126 | }
1127 | }
1128 |
1129 | @-o-keyframes frontgridfield__spinner__animation__keyframes {
1130 | 0% {
1131 | stroke-dashoffset: 140
1132 | }
1133 |
1134 | to {
1135 | stroke-dashoffset: 0
1136 | }
1137 | }
1138 |
1139 | @keyframes frontgridfield__spinner__animation__keyframes {
1140 | 0% {
1141 | stroke-dashoffset: 140
1142 | }
1143 |
1144 | to {
1145 | stroke-dashoffset: 0
1146 | }
1147 | }
1148 |
1149 | @-webkit-keyframes frontgridfield__spinner__animation__keyframes_ie {
1150 | 0% {
1151 | opacity: 0
1152 | }
1153 |
1154 | to {
1155 | opacity: 1
1156 | }
1157 | }
1158 |
1159 | @-o-keyframes frontgridfield__spinner__animation__keyframes_ie {
1160 | 0% {
1161 | opacity: 0
1162 | }
1163 |
1164 | to {
1165 | opacity: 1
1166 | }
1167 | }
1168 |
1169 | @keyframes frontgridfield__spinner__animation__keyframes_ie {
1170 | 0% {
1171 | opacity: 0
1172 | }
1173 |
1174 | to {
1175 | opacity: 1
1176 | }
1177 | }
1178 |
--------------------------------------------------------------------------------
/images/arrows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webbuilders-group/silverstripe-frontendgridfield/84f4bf6a0958960cc364d3fa1f7ffe5208155555/images/arrows.png
--------------------------------------------------------------------------------
/images/btn-icon/cross.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webbuilders-group/silverstripe-frontendgridfield/84f4bf6a0958960cc364d3fa1f7ffe5208155555/images/btn-icon/cross.png
--------------------------------------------------------------------------------
/images/btn-icon/document--pencil.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webbuilders-group/silverstripe-frontendgridfield/84f4bf6a0958960cc364d3fa1f7ffe5208155555/images/btn-icon/document--pencil.png
--------------------------------------------------------------------------------
/images/btn-icon/ellipsis.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webbuilders-group/silverstripe-frontendgridfield/84f4bf6a0958960cc364d3fa1f7ffe5208155555/images/btn-icon/ellipsis.png
--------------------------------------------------------------------------------
/images/btn-icon/magnifier.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webbuilders-group/silverstripe-frontendgridfield/84f4bf6a0958960cc364d3fa1f7ffe5208155555/images/btn-icon/magnifier.png
--------------------------------------------------------------------------------
/images/icons/add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webbuilders-group/silverstripe-frontendgridfield/84f4bf6a0958960cc364d3fa1f7ffe5208155555/images/icons/add.png
--------------------------------------------------------------------------------
/images/icons/chain--minus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webbuilders-group/silverstripe-frontendgridfield/84f4bf6a0958960cc364d3fa1f7ffe5208155555/images/icons/chain--minus.png
--------------------------------------------------------------------------------
/images/icons/chain--plus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webbuilders-group/silverstripe-frontendgridfield/84f4bf6a0958960cc364d3fa1f7ffe5208155555/images/icons/chain--plus.png
--------------------------------------------------------------------------------
/images/icons/cross-circle.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webbuilders-group/silverstripe-frontendgridfield/84f4bf6a0958960cc364d3fa1f7ffe5208155555/images/icons/cross-circle.png
--------------------------------------------------------------------------------
/images/icons/download-csv.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webbuilders-group/silverstripe-frontendgridfield/84f4bf6a0958960cc364d3fa1f7ffe5208155555/images/icons/download-csv.png
--------------------------------------------------------------------------------
/images/icons/filter-icons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webbuilders-group/silverstripe-frontendgridfield/84f4bf6a0958960cc364d3fa1f7ffe5208155555/images/icons/filter-icons.png
--------------------------------------------------------------------------------
/images/icons/pagination-arrows.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webbuilders-group/silverstripe-frontendgridfield/84f4bf6a0958960cc364d3fa1f7ffe5208155555/images/icons/pagination-arrows.png
--------------------------------------------------------------------------------
/javascript/FrontEndGridField.js:
--------------------------------------------------------------------------------
1 | (function($) {
2 | $.entwine('ss', function($) {
3 | $('.ss-gridfield').entwine({
4 | UUID: null,
5 |
6 | onmatch: function() {
7 | this._super();
8 | this.setUUID(new Date().getTime());
9 | },
10 |
11 | closeDialog: function() {
12 | var dialog=$('#ss-ui-dialog-'+(this.getUUID()));
13 |
14 | if(dialog.length>0) {
15 | dialog.fegfdialog('close');
16 | }
17 | }
18 | });
19 |
20 | //Init jQuery UI Buttons
21 | $('.ss-gridfield .ss-ui-button, .ss-gridfield .action, .ss-gridfield .trigger').entwine({
22 | onadd: function() {
23 | this.addClass('ss-ui-button');
24 | if(!this.data('button')) this.button();
25 | this._super();
26 | },
27 | onremove: function() {
28 | if(this.data('button')) this.button('destroy');
29 | this._super();
30 | }
31 | });
32 |
33 |
34 | $('.frontendgrid.ss-gridfield:not(.ss-gridfield-editable) .grid-field__col-compact .action.gridfield-button-delete, .frontendgrid.ss-gridfield:not(.ss-gridfield-editable) .col-buttons .action--delete').entwine({
35 | /**
36 | * Function: onclick
37 | */
38 | onclick: function(e) {
39 | // Confirmation on delete
40 | if(!confirm(ss.i18n._t('Admin.DELETECONFIRMMESSAGE', 'Are you sure you want to delete this record?'))) {
41 | e.preventDefault();
42 | return false;
43 | }
44 |
45 | if(!this.is(':disabled')) {
46 | var filterState='show'; //filterstate should equal current state.
47 |
48 | if(this.hasClass('ss-gridfield-button-close') || !(this.closest('.ss-gridfield').hasClass('show-filter'))){
49 | filterState='hidden';
50 | }
51 |
52 | this.getGridField().reload({data: [{name: this.attr('name'), value: this.val(), filter: filterState}]});
53 | }
54 |
55 | e.preventDefault();
56 | return false;
57 | }
58 | });
59 |
60 |
61 | //Row Click
62 | $('.ss-gridfield:not(.ss-gridfield-editable) .ss-gridfield-item:not(.ss-gridfield-no-items) td:not(.grid-field__col-compact)').entwine({
63 | /**
64 | * Function: onclick
65 | */
66 | onclick: function(e) {
67 | var editButton=$(this).parent().find('a.edit-link, a.view-link');
68 | if (editButton.length) {
69 | var self=this, id='ss-ui-dialog-'+this.getGridField().getUUID();
70 | var dialog=$('#'+id);
71 |
72 | if(!dialog.length) {
73 | dialog=$('
');
74 | $('body').append(dialog);
75 | }
76 |
77 | var extraClass=(this.data('popupclass') ? this.data('popupclass'):'');
78 | dialog.fegfdialog({
79 | title: editButton.text(),
80 | iframeUrl: editButton.attr('href'),
81 | autoOpen: true,
82 | dialogExtraClass: extraClass,
83 | close: function(e, ui) {
84 | self.getGridField().reload();
85 | }
86 | });
87 | }
88 | e.preventDefault();
89 | return false;
90 | }
91 | });
92 | $('.ss-gridfield:not(.ss-gridfield-editable) .ss-gridfield-item:not(.ss-gridfield-no-items) td.grid-field__col-compact').entwine({
93 | /**
94 | * Function: onclick
95 | */
96 | onclick: function(e) {
97 | e.preventDefault();
98 | return false;
99 | }
100 | });
101 |
102 | //View/Edit Button Click
103 | $('.ss-gridfield a.edit-link, .ss-gridfield a.view-link, .ss-gridfield a.new-link').entwine({
104 | /**
105 | * Function: onclick
106 | */
107 | onclick: function(e) {
108 | var self=this, id='ss-ui-dialog-'+this.getGridField().getUUID();
109 | var dialog=$('#'+id);
110 |
111 | if(!dialog.length) {
112 | dialog=$('');
113 | $('body').append(dialog);
114 | }
115 |
116 | var extraClass=(this.data('popupclass') ? this.data('popupclass'):'');
117 | dialog.fegfdialog({
118 | title: $(this).text(),
119 | iframeUrl: this.attr('href'),
120 | autoOpen: true,
121 | dialogExtraClass: extraClass,
122 | close: function(e, ui) {
123 | self.getGridField().reload();
124 | }
125 | });
126 |
127 | e.preventDefault();
128 | return false;
129 | }
130 | });
131 |
132 | $('.grid-field__table .ss-gridfield-item.loading').entwine({
133 | onmatch: function() {
134 | this.append(' \
135 |
\
136 |
\
137 |
\
138 |
\
175 |
\
176 |
\
177 |
');
178 |
179 | this._super();
180 | },
181 |
182 | onunmatch: function() {
183 | this.children('.cms-loading-container').remove();
184 |
185 | this._super();
186 | }
187 | });
188 | });
189 |
190 | $.widget("fegf.fegfdialog", $.ssui.ssdialog, {
191 | _resizeIframe: function() {
192 | //Call Parent
193 | $.ssui.ssdialog.prototype._resizeIframe.call(this);
194 |
195 | var iframe=this.element.children('iframe');
196 | var titlebar=jQuery(this.uiDialog).find('.ui-dialog-titlebar');
197 |
198 | //Resize the iframe taking into account the title bar
199 | if(titlebar.length>0 && titlebar.is(':visible')) {
200 | iframe.attr('height', iframe.attr('height')-titlebar.outerHeight());
201 | }
202 | }
203 | });
204 | })(jQuery);
--------------------------------------------------------------------------------
/javascript/GridField.js:
--------------------------------------------------------------------------------
1 | /*** Copied and modified from https://github.com/silverstripe/silverstripe-admin/tree/1.4 ***/
2 | (function($) {
3 | $.entwine('ss', function($) {
4 | $('.grid-field').entwine({
5 | onmatch: function () {
6 | if (this.needsColumnFix()) {
7 | this.fixColumns();
8 | this.injectSearchButton(false);
9 | }
10 |
11 | if (this.is('.grid-field--lazy-loadable') && (
12 | (this.closest('.ss-tabset').length === 0) || (this.data('gridfield-lazy-load-state') === 'force') )
13 | ) {
14 | // If our GridField is not inside a tabset for an immidiate reload
15 | this.data('gridfield-lazy-load-state', 'ready');
16 | this.lazyload();
17 | }
18 |
19 | this.data('gridfield-lazy-load-state', 'ready');
20 | },
21 |
22 | /**
23 | * @func Trigger a lazy load on this gridfield
24 | */
25 | lazyload: function() {
26 | if (this.data('gridfield-lazy-load-state') !== 'ready') {
27 | this.data('gridfield-lazy-load-state', 'force');
28 | } else {
29 | this.removeClass('grid-field--lazy-loadable').addClass('grid-field--lazy-loaded');
30 | this.reload();
31 | }
32 |
33 | },
34 |
35 | /**
36 | * @param {Object} Additional options for jQuery.ajax() call
37 | * @param {successCallback} callback to call after reloading succeeded.
38 | */
39 | reload: function(ajaxOpts, successCallback) {
40 | var self = this, form = this.closest('form'),
41 | focusedElName = this.find(':input:focus').attr('name'), // Save focused element for restoring after refresh
42 | data = form.find(':input:not(.grid-field__search-holder :input, .relation-search)').serializeArray(),
43 | tbody = this.find('tbody'),
44 | colspan = this.find('.grid-field__title-row th').attr('colspan');
45 | ;
46 |
47 | if(!ajaxOpts) ajaxOpts = {};
48 | if(!ajaxOpts.data) ajaxOpts.data = [];
49 | ajaxOpts.data = ajaxOpts.data.concat(data);
50 |
51 |
52 | // Include any GET parameters from the current URL, as the view state might depend on it.
53 | // For example, a list prefiltered through external search criteria might be passed to GridField.
54 | if(window.location.search) {
55 | ajaxOpts.data = window.location.search.replace(/^\?/, '') + '&' + $.param(ajaxOpts.data);
56 | }
57 |
58 | // Enable loading animation
59 | tbody.find('tr').remove();
60 | var loadingCell = $(' | ')
61 | .addClass('ss-gridfield-item loading')
62 | .attr('colspan', colspan);
63 | tbody.append($('
').append(loadingCell));
64 |
65 | var request = $.ajax($.extend({}, {
66 | headers: {"X-Pjax" : 'CurrentField'},
67 | type: "POST",
68 | url: this.data('url'),
69 | dataType: 'html',
70 | success: function (data) {
71 | // Replace the grid field with response, not the form.
72 | // TODO Only replaces all its children, to avoid replacing the current scope
73 | // of the executing method. Means that it doesn't retrigger the onmatch() on the main container.
74 | self.empty().append($(data).children());
75 |
76 | // Refocus previously focused element. Useful e.g. for finding+adding
77 | // multiple relationships via keyboard.
78 | if(focusedElName) self.find(':input[name="' + focusedElName + '"]').focus();
79 |
80 | // Update filter
81 | if (self.find('.grid-field__filter-header, .grid-field__search-holder').length) {
82 | var visible = ajaxOpts.data[0].filter === "show";
83 | if (self.needsColumnFix()) {
84 | self.fixColumns();
85 | }
86 | self.injectSearchButton(visible);
87 | }
88 |
89 | if(successCallback) successCallback.apply(this, arguments);
90 | self.trigger('reload', self);
91 |
92 | // Trigger change if it's not specifically supressed
93 | if (ajaxOpts.data[0].triggerChange !== false) {
94 | self.trigger('change');
95 | }
96 | },
97 | error: function(e) {
98 | alert(i18n._t('Admin.ERRORINTRANSACTION'));
99 | },
100 | complete: function(request, status) {
101 | self.find('.loading').removeClass('loading');
102 | }
103 | }, ajaxOpts));
104 | },
105 | showDetailView: function(url) {
106 | window.location.href = url;
107 | },
108 | getItems: function() {
109 | return this.find('.ss-gridfield-item');
110 | },
111 | /**
112 | * @param {String}
113 | * @param {Mixed}
114 | */
115 | setState: function(k, v) {
116 | var state = this.getState();
117 | state[k] = v;
118 | this.find(':input[name="' + this.data('name') + '[GridState]"]').val(JSON.stringify(state));
119 | },
120 | /**
121 | * @return {Object}
122 | */
123 | getState: function() {
124 | return JSON.parse(this.find(':input[name="' + this.data('name') + '[GridState]"]').val());
125 | },
126 |
127 | needsColumnFix: function() {
128 | return (
129 | this.find('.grid-field__filter-header, .grid-field__search-holder').length &&
130 | !this.find('.grid-field__col-compact').length &&
131 | !this.find('th.col-Actions').length
132 | );
133 | },
134 |
135 | fixColumns: function (visible) {
136 | this.find('.sortable-header').append(' | ');
137 | this.find('tbody tr').each(function () {
138 | var cell = $(this).find('td:last');
139 | cell.attr('colspan', 2);
140 | });
141 | var $extraCell = $('');
142 | $('.grid-field__filter-header th:last .action').each(function() {
143 | $(this).detach();
144 | $extraCell.append($(this));
145 | });
146 | $('.grid-field__filter-header').append($extraCell);
147 | },
148 |
149 | injectSearchButton: function(visible) {
150 | const hasLegacyFilterHeader = this.find('.grid-field__filter-header').length > 0;
151 | let content;
152 | if (visible) {
153 | content = '';
154 | this.addClass('show-filter').find('.grid-field__filter-header, .grid-field__search-holder').removeClass('grid-field__search-holder--hidden');
155 | if (!hasLegacyFilterHeader) {
156 | this.find(':button[name=showFilter]').hide();
157 | }
158 | } else {
159 | content = '';
160 | this.removeClass('show-filter').find('.grid-field__filter-header, .grid-field__search-holder').addClass('grid-field__search-holder--hidden');
161 | }
162 | if (hasLegacyFilterHeader) {
163 | this.find('.sortable-header th:last').html(content);
164 | }
165 | }
166 | });
167 |
168 | $('.grid-field *').entwine({
169 | getGridField: function() {
170 | return this.closest('.grid-field');
171 | }
172 | });
173 |
174 |
175 | $('.grid-field :button[name=showFilter]').entwine({
176 | onclick: function(e) {
177 | this.closest('.grid-field')
178 | .find('.grid-field__filter-header, .grid-field__search-holder')
179 | .removeClass('grid-field__search-holder--hidden')
180 | .find(':input:first').focus(); // focus first search field
181 |
182 | this.closest('.grid-field').addClass('show-filter');
183 | this.parent().html('');
184 | e.preventDefault();
185 | }
186 | });
187 |
188 |
189 | $('.grid-field .ss-gridfield-item').entwine({
190 | onclick: function (e) {
191 | if (e.target.classList.contains('action-menu__toggle')) {
192 | this._super(e);
193 | return false;
194 | }
195 |
196 | if($(e.target).closest('.action').length) {
197 | this._super(e);
198 | return false;
199 | }
200 |
201 | var formLink = this.find('.edit-link, .view-link');
202 | if(formLink.length) this.getGridField().showDetailView(formLink.prop('href'));
203 | },
204 | onmouseover: function() {
205 | if(this.find('.edit-link, .view-link').length) this.css('cursor', 'pointer');
206 | },
207 | onmouseout: function() {
208 | this.css('cursor', 'default');
209 | }
210 | });
211 |
212 | $('.grid-field .action.action_import:button').entwine({
213 | onclick: function(e) {
214 | e.preventDefault();
215 | this.openmodal();
216 | },
217 | onmatch: function() {
218 | this._super();
219 | // Trigger auto-open
220 | if (this.data('state') === 'open') {
221 | this.openmodal();
222 | }
223 | },
224 | onunmatch: function() {
225 | this._super();
226 | },
227 |
228 | openmodal: function() {
229 | // Remove existing modal
230 | let modal = $(this.data('target'));
231 | let newModal = $(this.data('modal'));
232 | if (modal.length < 1) {
233 | // Add modal to end of body tag
234 | modal = newModal;
235 | modal.appendTo(document.body);
236 | } else {
237 | // Replace inner content
238 | modal.innerHTML = newModal.innerHTML;
239 | }
240 |
241 | // Apply backdrop
242 | let backdrop = $('.modal-backdrop');
243 | if(backdrop.length < 1) {
244 | backdrop = $('');
245 | backdrop.appendTo(document.body);
246 | }
247 |
248 | function closeModal() {
249 | backdrop.removeClass('show');
250 | modal.removeClass('show');
251 | setTimeout(function() {
252 | backdrop.remove();
253 | }, 150) // Simulate the bootstrap out-transition period
254 | }
255 |
256 | // Set close action
257 | modal.find('[data-dismiss]').add('.modal-backdrop')
258 | .on('click', function () {
259 | closeModal();
260 | });
261 |
262 | $(document).on('keydown', function(e) {
263 | if (e.keyCode === 27) { // Escape key
264 | closeModal();
265 | }
266 | });
267 |
268 | // Fade each element in (use setTimeout to ensure initial render at opacity=0 works)
269 | setTimeout(function() {
270 | backdrop.addClass('show');
271 | modal.addClass('show');
272 | }, 0);
273 |
274 | }
275 | });
276 |
277 | $('.grid-field .action:button').entwine({
278 | onclick: function (e) {
279 | var filterState = 'show'; //filterstate should equal current state.
280 | let triggerChange = true;
281 |
282 | // If the button is disabled, do nothing.
283 | if (this.is(':disabled')) {
284 | e.preventDefault();
285 | return;
286 | }
287 |
288 | if(this.hasClass('ss-gridfield-button-close') || !(this.closest('.grid-field').hasClass('show-filter'))) {
289 | filterState = 'hidden';
290 | }
291 |
292 | if (this.hasClass('ss-gridfield-pagination-action') || this.hasClass('grid-field__sort')) {
293 | triggerChange = false;
294 | }
295 |
296 | const successCallback = function(data, status, response) {
297 | const messageText = response.getResponseHeader('X-Message-Text');
298 | const messageType = response.getResponseHeader('X-Message-Type');
299 | if (messageText && messageType) {
300 | var formEditError = $("#Form_EditForm_error");
301 | formEditError.addClass(messageType);
302 | formEditError.html(messageText);
303 | formEditError.show();
304 | }
305 | };
306 |
307 | var data = [
308 | {
309 | name: this.attr('name'),
310 | value: this.val(),
311 | filter: filterState,
312 | triggerChange: triggerChange
313 | },
314 | ];
315 |
316 | var actionState = this.data('action-state');
317 | if (actionState) {
318 | data.push({
319 | name: 'ActionState',
320 | value: JSON.stringify(actionState),
321 | });
322 | }
323 |
324 | this.getGridField().reload(
325 | { data: data },
326 | successCallback
327 | );
328 |
329 | e.preventDefault();
330 | },
331 | /**
332 | * Get the url this action should submit to
333 | */
334 | actionurl: function () {
335 | var btn = this.closest(':button'), grid = this.getGridField(),
336 | form = this.closest('form'), data = form.find(':input.gridstate').serialize(),
337 | csrf = form.find('input[name="SecurityID"]').val();
338 |
339 | // Add current button
340 | data += "&" + encodeURIComponent(btn.attr('name')) + '=' + encodeURIComponent(btn.val());
341 |
342 | // Add csrf
343 | if(csrf) {
344 | data += "&SecurityID=" + encodeURIComponent(csrf);
345 | }
346 |
347 | // Add action data
348 | var actionState = this.data('action-state');
349 | if (actionState) {
350 | data += '&ActionState=' + encodeURIComponent(JSON.stringify(actionState))
351 | }
352 |
353 | // Include any GET parameters from the current URL, as the view
354 | // state might depend on it. For example, a list pre-filtered
355 | // through external search criteria might be passed to GridField.
356 | if(window.location.search) {
357 | data = window.location.search.replace(/^\?/, '') + '&' + data;
358 | }
359 |
360 | // decide whether we should use ? or & to connect the URL
361 | var connector = grid.data('url').indexOf('?') == -1 ? '?' : '&';
362 |
363 | return jQuery.path.makeUrlAbsolute(
364 | grid.data('url') + connector + data,
365 | $('base').attr('href')
366 | );
367 | }
368 |
369 | });
370 |
371 | /**
372 | * Don't allow users to submit empty values in grid field auto complete inputs.
373 | */
374 | $('.grid-field .add-existing-autocompleter').entwine({
375 | onbuttoncreate: function () {
376 | var self = this;
377 |
378 | this.toggleDisabled();
379 |
380 | this.find('input[type="text"]').on('keyup', function () {
381 | self.toggleDisabled();
382 | });
383 | },
384 | onunmatch: function () {
385 | this.find('input[type="text"]').off('keyup');
386 | },
387 | toggleDisabled: function () {
388 | var $button = this.find('.ss-ui-button'),
389 | $input = this.find('input[type="text"]'),
390 | inputHasValue = $input.val() !== '',
391 | buttonDisabled = $button.is(':disabled');
392 |
393 | if ((inputHasValue && buttonDisabled) || (!inputHasValue && !buttonDisabled)) {
394 | $button.button("option", "disabled", !buttonDisabled);
395 | }
396 | }
397 | });
398 |
399 | // Covers both tabular delete button, and the button on the detail form
400 | $('.grid-field .grid-field__col-compact .action--delete, .grid-field .grid-field__col-compact .action--archive, .cms-edit-form .btn-toolbar .action--delete, .cms-edit-form .btn-toolbar .action--archive').entwine({
401 | onclick: function(e){
402 | const confirmMessage = $(this).hasClass('action--archive')
403 | ? i18n._t('Admin.ARCHIVECONFIRMMESSAGE', 'Are you sure you want to archive this record?')
404 | : i18n._t('Admin.DELETECONFIRMMESSAGE', 'Are you sure you want to delete this record?');
405 |
406 | if (!confirm(confirmMessage)) {
407 | e.preventDefault();
408 | return false;
409 | } else {
410 | this._super(e);
411 | }
412 | }
413 | });
414 |
415 | $('.grid-field .grid-print-button.action:button').entwine({
416 | UUID: null,
417 | onmatch: function() {
418 | this._super();
419 | this.setUUID(new Date().getTime());
420 | },
421 | onunmatch: function() {
422 | this._super();
423 | },
424 | onclick: function(e) {
425 | var url = this.actionurl();
426 | window.open(url);
427 | e.preventDefault();
428 | return false;
429 | }
430 | });
431 |
432 | $('.ss-gridfield-print-iframe').entwine({
433 | onmatch: function(){
434 | this._super();
435 |
436 | this.hide().bind('load', function() {
437 | this.focus();
438 | var ifWin = this.contentWindow || this;
439 | ifWin.print();
440 | });
441 | },
442 | onunmatch: function() {
443 | this._super();
444 | }
445 | });
446 |
447 | /**
448 | * Prevents actions from causing an ajax reload of the field.
449 | *
450 | * Useful e.g. for actions which rely on HTTP response headers being
451 | * interpreted natively by the browser, like file download triggers.
452 | */
453 | $('.grid-field .action.no-ajax, .grid-field .no-ajax .action:button').entwine({
454 | onclick: function(e){
455 | window.location.href = this.actionurl();
456 | e.preventDefault();
457 | return false;
458 | }
459 | });
460 |
461 | $('.grid-field .action-detail').entwine({
462 | onclick: function() {
463 | this.getGridField().showDetailView($(this).prop('href'));
464 | return false;
465 | }
466 | });
467 |
468 | /**
469 | * Allows selection of one or more rows in the grid field.
470 | * Purely clientside at the moment.
471 | */
472 | $('.grid-field[data-selectable]').entwine({
473 | /**
474 | * @return {jQuery} Collection
475 | */
476 | getSelectedItems: function() {
477 | return this.find('.ss-gridfield-item.ui-selected');
478 | },
479 | /**
480 | * @return {Array} Of record IDs
481 | */
482 | getSelectedIDs: function() {
483 | return $.map(this.getSelectedItems(), function(el) {return $(el).data('id');});
484 | }
485 | });
486 | $('.grid-field[data-selectable] .ss-gridfield-items').entwine({
487 | onadd: function() {
488 | this._super();
489 |
490 | // TODO Limit to single selection
491 | this.selectable();
492 | },
493 | onremove: function() {
494 | this._super();
495 | if (this.data('selectable')) this.selectable('destroy');
496 | }
497 | });
498 |
499 | /**
500 | * Catch submission event in filter input fields, and submit the correct button
501 | * rather than the whole form.
502 | */
503 | $('.grid-field .grid-field__filter-header :input').entwine({
504 | onmatch: function() {
505 | var filterbtn = this.closest('.extra').find('.ss-gridfield-button-filter'),
506 | resetbtn = this.closest('.extra').find('.ss-gridfield-button-reset');
507 |
508 | if(this.val()) {
509 | filterbtn.addClass('filtered');
510 | resetbtn.addClass('filtered');
511 | }
512 | this._super();
513 | },
514 | onunmatch: function() {
515 | this._super();
516 | },
517 | onkeydown: function(e) {
518 | // Skip reset button events, they should trigger default submission
519 | if(this.closest('.ss-gridfield-button-reset').length) return;
520 |
521 | var filterbtn = this.closest('.extra').find('.ss-gridfield-button-filter'),
522 | resetbtn = this.closest('.extra').find('.ss-gridfield-button-reset');
523 |
524 | if(e.keyCode == '13') {
525 | var btns = this.closest('.grid-field__filter-header').find('button.ss-gridfield-button-filter');
526 | var filterState='show'; //filterstate should equal current state.
527 | if(this.hasClass('ss-gridfield-button-close')||!(this.closest('.grid-field').hasClass('show-filter'))){
528 | filterState='hidden';
529 | }
530 |
531 | var ajaxData = [{
532 | name: btns.attr('name'),
533 | value: btns.val(),
534 | filter: filterState,
535 | triggerChange: false
536 | }];
537 |
538 | if (btns.data('action-state')) {
539 | ajaxData.push({
540 | name: 'ActionState',
541 | value: JSON.stringify(btns.data('action-state')),
542 | });
543 | }
544 |
545 | this.getGridField().reload({
546 | data: ajaxData
547 | });
548 | return false;
549 | }else{
550 | filterbtn.addClass('hover-alike');
551 | resetbtn.addClass('hover-alike');
552 | }
553 | }
554 | });
555 |
556 | $(".grid-field .relation-search").entwine({
557 | onfocusin: function (event) {
558 | this.autocomplete({
559 | source: function(request, response){
560 | var searchField = $(this.element);
561 | var form = $(this.element).closest("form");
562 | $.ajax({
563 | headers: {
564 | "X-Pjax" : 'Partial'
565 | },
566 | dataType: 'json',
567 | type: "GET",
568 | url: $(searchField).data('searchUrl'),
569 | data: encodeURIComponent(searchField.attr('name'))+'='+encodeURIComponent(searchField.val()),
570 | success: response,
571 | error: function(e) {
572 | alert(i18n._t('Admin.ERRORINTRANSACTION', 'An error occured while fetching data from the server\n Please try again later.'));
573 | }
574 | });
575 | },
576 | select: function(event, ui) {
577 | var hiddenField = $('');
578 | hiddenField.val(ui.item.id);
579 | $(this)
580 | .closest(".grid-field")
581 | .find(".action_gridfield_relationfind")
582 | .replaceWith(hiddenField);
583 | var addbutton = $(this).closest(".grid-field").find(".action_gridfield_relationadd");
584 |
585 | addbutton.removeAttr('disabled');
586 | }
587 | });
588 | }
589 | });
590 |
591 | $(".grid-field .pagination-page-number input").entwine({
592 | onkeydown: function(event) {
593 | if(event.keyCode == 13) {
594 | event.preventDefault();
595 | var newpage = parseInt($(this).val(), 10);
596 |
597 | var gridfield = $(this).getGridField();
598 | gridfield.setState('GridFieldPaginator', {currentPage: newpage});
599 | gridfield.reload();
600 |
601 | return false;
602 | }
603 | }
604 | });
605 | });
606 | })(jQuery);
--------------------------------------------------------------------------------
/javascript/boot.template.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var configDefault = {
3 | SecurityID: '$SecurityID',
4 | absoluteBaseUrl: '$AbsoluteBaseURL',
5 | baseUrl: '$BaseURL',
6 | adminUrl: 'admin/',
7 | environment: '$Environment',
8 | debugging: $Debugging,
9 | sections: [
10 | {
11 | name: 'SilverStripe\\Admin\\LeftAndMain',
12 | url: 'admin',
13 | graphql: {
14 | cachedTypenames: false,
15 | },
16 | },
17 | ],
18 | };
19 |
20 | window.ss = window.ss || {};
21 | window.ss.config = window.ss.config || configDefault;
22 | })();
--------------------------------------------------------------------------------
/javascript/externals/hafriedlander/jquery-entwine/jquery.entwine-dist.js:
--------------------------------------------------------------------------------
1 | /* jQuery.Entwine - Copyright 2009-2011 Hamish Friedlander and SilverStripe. Version . */
2 |
3 | /* vendor/jquery.selector/jquery.class.js */
4 |
5 | /**
6 | * Very basic Class utility. Based on base and jquery.class.
7 | *
8 | * Class definition: var Foo = Base.extend({ init: function(){ Constructor }; method_name: function(){ Method } });
9 | *
10 | * Inheritance: var Bar = Foo.extend({ method_name: function(){ this._super(); } });
11 | *
12 | * new-less Constructor: new Foo(arg) <-same as-> Foo(arg)
13 | */
14 |
15 | var Base;
16 |
17 | (function(){
18 |
19 | var marker = {}, fnTest = /xyz/.test(function(){var xyz;}) ? /\b_super\b/ : /.*/;
20 |
21 | // The base Class implementation (does nothing)
22 | Base = function(){};
23 |
24 | Base.addMethod = function(name, func) {
25 | var parent = this._super && this._super.prototype;
26 |
27 | if (parent && fnTest.test(func)) {
28 | this.prototype[name] = function(){
29 | var tmp = this._super;
30 | this._super = parent[name];
31 | try {
32 | var ret = func.apply(this, arguments);
33 | }
34 | finally {
35 | this._super = tmp;
36 | }
37 | return ret;
38 | };
39 | }
40 | else this.prototype[name] = func;
41 | };
42 |
43 | Base.addMethods = function(props) {
44 | for (var name in props) {
45 | if (typeof props[name] == 'function') this.addMethod(name, props[name]);
46 | else this.prototype[name] = props[name];
47 | }
48 | };
49 |
50 | Base.subclassOf = function(parentkls) {
51 | var kls = this;
52 | while (kls) {
53 | if (kls === parentkls) return true;
54 | kls = kls._super;
55 | }
56 | };
57 |
58 | // Create a new Class that inherits from this class
59 | Base.extend = function(props) {
60 |
61 | // The dummy class constructor
62 | var Kls = function() {
63 | if (arguments[0] === marker) return;
64 |
65 | if (this instanceof Kls) {
66 | if (this.init) this.init.apply(this, arguments);
67 | }
68 | else {
69 | var ret = new Kls(marker); if (ret.init) ret.init.apply(ret, arguments); return ret;
70 | }
71 | };
72 |
73 | // Add the common class variables and methods
74 | Kls.constructor = Kls;
75 | Kls.extend = Base.extend;
76 | Kls.addMethod = Base.addMethod;
77 | Kls.addMethods = Base.addMethods;
78 | Kls.subclassOf = Base.subclassOf;
79 |
80 | Kls._super = this;
81 |
82 | // Attach the parent object to the inheritance chain
83 | Kls.prototype = new this(marker);
84 | Kls.prototype.constructor = Kls;
85 |
86 | // Copy the properties over onto the new prototype
87 | Kls.addMethods(props);
88 |
89 | return Kls;
90 | };
91 | })();;
92 |
93 |
94 | /* vendor/jquery.selector/jquery.selector.js */
95 |
96 | (function($){
97 |
98 | var tokens = {
99 | UNICODE: /\\[0-9a-f]{1,6}(?:\r\n|[ \n\r\t\f])?/,
100 | ESCAPE: /(?:UNICODE)|\\[^\n\r\f0-9a-f]/,
101 | NONASCII: /[^\x00-\x7F]/,
102 | NMSTART: /[_a-z]|(?:NONASCII)|(?:ESCAPE)/,
103 | NMCHAR: /[_a-z0-9-]|(?:NONASCII)|(?:ESCAPE)/,
104 | IDENT: /-?(?:NMSTART)(?:NMCHAR)*/,
105 |
106 | NL: /\n|\r\n|\r|\f/,
107 |
108 | STRING: /(?:STRING1)|(?:STRING2)|(?:STRINGBARE)/,
109 | STRING1: /"(?:(?:ESCAPE)|\\(?:NL)|[^\n\r\f\"])*"/,
110 | STRING2: /'(?:(?:ESCAPE)|\\(?:NL)|[^\n\r\f\'])*'/,
111 | STRINGBARE: /(?:(?:ESCAPE)|\\(?:NL)|[^\n\r\f\]])*/,
112 |
113 | FUNCTION: /(?:IDENT)\(\)/,
114 |
115 | INTEGER: /[0-9]+/,
116 |
117 | WITHN: /([-+])?(INTEGER)?(n)\s*(?:([-+])\s*(INTEGER))?/,
118 | WITHOUTN: /([-+])?(INTEGER)/
119 | };
120 |
121 | var rx = {
122 | not: /:not\(/,
123 | not_end: /\)/,
124 |
125 | tag: /((?:IDENT)|\*)/,
126 | id: /#(IDENT)/,
127 | cls: /\.(IDENT)/,
128 | attr: /\[\s*(IDENT)\s*(?:([^=]?=)\s*(STRING)\s*)?\]/,
129 | pseudo_el: /(?::(first-line|first-letter|before|after))|(?:::((?:FUNCTION)|(?:IDENT)))/,
130 | pseudo_cls_nth: /:nth-child\(\s*(?:(?:WITHN)|(?:WITHOUTN)|(odd|even))\s*\)/,
131 | pseudo_cls: /:(IDENT)/,
132 |
133 | comb: /\s*(\+|~|>)\s*|\s+/,
134 | comma: /\s*,\s*/,
135 | important: /\s+!important\s*$/
136 | };
137 |
138 | /* Replace placeholders with actual regex, and mark all as case insensitive */
139 | var token = /[A-Z][A-Z0-9]+/;
140 | for (var k in rx) {
141 | var m, src = rx[k].source;
142 | while (m = src.match(token)) src = src.replace(m[0], tokens[m[0]].source);
143 | rx[k] = new RegExp(src, 'gi');
144 | }
145 |
146 | /**
147 | * A string that matches itself against regexii, and keeps track of how much of itself has been matched
148 | */
149 | var ConsumableString = Base.extend({
150 | init: function(str) {
151 | this.str = str;
152 | this.pos = 0;
153 | },
154 | match: function(rx) {
155 | var m;
156 | rx.lastIndex = this.pos;
157 | if ((m = rx.exec(this.str)) && m.index == this.pos ) {
158 | this.pos = rx.lastIndex ? rx.lastIndex : this.str.length ;
159 | return m;
160 | }
161 | return null;
162 | },
163 | peek: function(rx) {
164 | var m;
165 | rx.lastIndex = this.pos;
166 | if ((m = rx.exec(this.str)) && m.index == this.pos ) return m;
167 | return null;
168 | },
169 | showpos: function() {
170 | return this.str.slice(0,this.pos)+'' + this.str.slice(this.pos);
171 | },
172 | done: function() {
173 | return this.pos == this.str.length;
174 | }
175 | });
176 |
177 | /* A base class that all Selectors inherit off */
178 | var SelectorBase = Base.extend({});
179 |
180 | /**
181 | * A class representing a Simple Selector, as per the CSS3 selector spec
182 | */
183 | var SimpleSelector = SelectorBase.extend({
184 | init: function() {
185 | this.tag = null;
186 | this.id = null;
187 | this.classes = [];
188 | this.attrs = [];
189 | this.nots = [];
190 | this.pseudo_classes = [];
191 | this.pseudo_els = [];
192 | },
193 | parse: function(selector) {
194 | var m;
195 |
196 | /* Pull out the initial tag first, if there is one */
197 | if (m = selector.match(rx.tag)) this.tag = m[1];
198 |
199 | /* Then for each selection type, try and find a match */
200 | do {
201 | if (m = selector.match(rx.not)) {
202 | this.nots[this.nots.length] = SelectorsGroup().parse(selector);
203 | if (!(m = selector.match(rx.not_end))) {
204 | throw 'Invalid :not term in selector';
205 | }
206 | }
207 | else if (m = selector.match(rx.id)) this.id = m[1];
208 | else if (m = selector.match(rx.cls)) this.classes[this.classes.length] = m[1];
209 | else if (m = selector.match(rx.attr)) this.attrs[this.attrs.length] = [ m[1], m[2], m[3] ];
210 | else if (m = selector.match(rx.pseudo_el)) this.pseudo_els[this.pseudo_els.length] = m[1] || m[2];
211 | else if (m = selector.match(rx.pseudo_cls_nth)) {
212 | if (m[3]) {
213 | var a = parseInt((m[1]||'')+(m[2]||'1'));
214 | var b = parseInt((m[4]||'')+(m[5]||'0'));
215 | }
216 | else {
217 | var a = m[8] ? 2 : 0;
218 | var b = m[8] ? (4-m[8].length) : parseInt((m[6]||'')+m[7]);
219 | }
220 | this.pseudo_classes[this.pseudo_classes.length] = ['nth-child', [a, b]];
221 | }
222 | else if (m = selector.match(rx.pseudo_cls)) this.pseudo_classes[this.pseudo_classes.length] = [m[1]];
223 |
224 | } while(m && !selector.done());
225 |
226 | return this;
227 | }
228 | });
229 |
230 | /**
231 | * A class representing a Selector, as per the CSS3 selector spec
232 | */
233 | var Selector = SelectorBase.extend({
234 | init: function(){
235 | this.parts = [];
236 | },
237 | parse: function(cons){
238 | this.parts[this.parts.length] = SimpleSelector().parse(cons);
239 |
240 | while (!cons.done() && !cons.peek(rx.comma) && (m = cons.match(rx.comb))) {
241 | this.parts[this.parts.length] = m[1] || ' ';
242 | this.parts[this.parts.length] = SimpleSelector().parse(cons);
243 | }
244 |
245 | return this.parts.length == 1 ? this.parts[0] : this;
246 | }
247 | });
248 |
249 | /**
250 | * A class representing a sequence of selectors, as per the CSS3 selector spec
251 | */
252 | var SelectorsGroup = SelectorBase.extend({
253 | init: function(){
254 | this.parts = [];
255 | },
256 | parse: function(cons){
257 | this.parts[this.parts.length] = Selector().parse(cons);
258 |
259 | while (!cons.done() && (m = cons.match(rx.comma))) {
260 | this.parts[this.parts.length] = Selector().parse(cons);
261 | }
262 |
263 | return this.parts.length == 1 ? this.parts[0] : this;
264 | }
265 | });
266 |
267 |
268 | $.selector = function(s){
269 | var cons = ConsumableString(s);
270 | var res = SelectorsGroup().parse(cons);
271 |
272 | res.selector = s;
273 |
274 | if (!cons.done()) throw 'Could not parse selector - ' + cons.showpos() ;
275 | else return res;
276 | };
277 |
278 | $.selector.SelectorBase = SelectorBase;
279 | $.selector.SimpleSelector = SimpleSelector;
280 | $.selector.Selector = Selector;
281 | $.selector.SelectorsGroup = SelectorsGroup;
282 |
283 | })(jQuery);
284 | ;
285 |
286 |
287 | /* vendor/jquery.selector/jquery.selector.specifity.js */
288 |
289 | (function($) {
290 |
291 | $.selector.SimpleSelector.addMethod('specifity', function() {
292 | if (this.spec) return this.spec;
293 |
294 | var spec = [
295 | this.id ? 1 : 0,
296 | this.classes.length + this.attrs.length + this.pseudo_classes.length,
297 | ((this.tag && this.tag != '*') ? 1 : 0) + this.pseudo_els.length
298 | ];
299 | $.each(this.nots, function(i,not){
300 | var ns = not.specifity(); spec[0] += ns[0]; spec[1] += ns[1]; spec[2] += ns[2];
301 | });
302 |
303 | return this.spec = spec;
304 | });
305 |
306 | $.selector.Selector.addMethod('specifity', function(){
307 | if (this.spec) return this.spec;
308 |
309 | var spec = [0,0,0];
310 | $.each(this.parts, function(i,part){
311 | if (i%2) return;
312 | var ps = part.specifity(); spec[0] += ps[0]; spec[1] += ps[1]; spec[2] += ps[2];
313 | });
314 |
315 | return this.spec = spec;
316 | });
317 |
318 | $.selector.SelectorsGroup.addMethod('specifity', function(){
319 | if (this.spec) return this.spec;
320 |
321 | var spec = [0,0,0];
322 | $.each(this.parts, function(i,part){
323 | var ps = part.specifity(); spec[0] += ps[0]; spec[1] += ps[1]; spec[2] += ps[2];
324 | });
325 |
326 | return this.spec = spec;
327 | });
328 |
329 |
330 | })(jQuery);
331 | ;
332 |
333 |
334 | /* vendor/jquery.selector/jquery.selector.matches.js */
335 |
336 | /*
337 | This attempts to do the opposite of Sizzle.
338 | Sizzle is good for finding elements for a selector, but not so good for telling if an individual element matches a selector
339 | */
340 |
341 | (function($) {
342 |
343 | /**** CAPABILITY TESTS ****/
344 | var div = document.createElement('div');
345 | div.innerHTML = '';
346 |
347 | // In IE 6-7, getAttribute often does the wrong thing (returns similar to el.attr), so we need to use getAttributeNode on that browser
348 | var getAttributeDodgy = div.firstChild.getAttribute('id') !== 'test';
349 |
350 | // Does browser support Element.firstElementChild, Element.previousElementSibling, etc.
351 | var hasElementTraversal = div.firstElementChild && div.firstElementChild.tagName == 'FORM';
352 |
353 | // Does browser support Element.children
354 | var hasChildren = div.children && div.children[0].tagName == 'FORM';
355 |
356 | /**** INTRO ****/
357 |
358 | var GOOD = /GOOD/g;
359 | var BAD = /BAD/g;
360 |
361 | var STARTS_WITH_QUOTES = /^['"]/g;
362 |
363 | var join = function(js) {
364 | return js.join('\n');
365 | };
366 |
367 | var join_complex = function(js) {
368 | var code = new String(js.join('\n')); // String objects can have properties set. strings can't
369 | code.complex = true;
370 | return code;
371 | };
372 |
373 | /**** ATTRIBUTE ACCESSORS ****/
374 |
375 | // Not all attribute names can be used as identifiers, so we encode any non-acceptable characters as hex
376 | var varForAttr = function(attr) {
377 | return '_' + attr.replace(/^[^A-Za-z]|[^A-Za-z0-9]/g, function(m){ return '_0x' + m.charCodeAt(0).toString(16) + '_'; });
378 | };
379 |
380 | var getAttr;
381 |
382 | // Good browsers
383 | if (!getAttributeDodgy) {
384 | getAttr = function(attr){ return 'var '+varForAttr(attr)+' = el.getAttribute("'+attr+'");' ; };
385 | }
386 | // IE 6, 7
387 | else {
388 | // On IE 6 + 7, getAttribute still has to be called with DOM property mirror name, not attribute name. Map attributes to those names
389 | var getAttrIEMap = { 'class': 'className', 'for': 'htmlFor' };
390 |
391 | getAttr = function(attr) {
392 | var ieattr = getAttrIEMap[attr] || attr;
393 | return 'var '+varForAttr(attr)+' = el.getAttribute("'+ieattr+'",2) || (el.getAttributeNode("'+attr+'")||{}).nodeValue;';
394 | };
395 | }
396 |
397 | /**** ATTRIBUTE COMPARITORS ****/
398 |
399 | var attrchecks = {
400 | '-': '!K',
401 | '=': 'K != "V"',
402 | '!=': 'K == "V"',
403 | '~=': '_WS_K.indexOf(" V ") == -1',
404 | '^=': '!K || K.indexOf("V") != 0',
405 | '*=': '!K || K.indexOf("V") == -1',
406 | '$=': '!K || K.substr(K.length-"V".length) != "V"'
407 | };
408 |
409 | /**** STATE TRACKER ****/
410 |
411 | var State = $.selector.State = Base.extend({
412 | init: function(){
413 | this.reset();
414 | },
415 | reset: function() {
416 | this.attrs = {}; this.wsattrs = {};
417 | },
418 |
419 | prev: function(){
420 | this.reset();
421 | if (hasElementTraversal) return 'el = el.previousElementSibling';
422 | return 'while((el = el.previousSibling) && el.nodeType != 1) {}';
423 | },
424 | next: function() {
425 | this.reset();
426 | if (hasElementTraversal) return 'el = el.nextElementSibling';
427 | return 'while((el = el.nextSibling) && el.nodeType != 1) {}';
428 | },
429 | prevLoop: function(body){
430 | this.reset();
431 | if (hasElementTraversal) return join([ 'while(el = el.previousElementSibling){', body]);
432 | return join([
433 | 'while(el = el.previousSibling){',
434 | 'if (el.nodeType != 1) continue;',
435 | body
436 | ]);
437 | },
438 | parent: function() {
439 | this.reset();
440 | return 'el = el.parentNode;';
441 | },
442 | parentLoop: function(body) {
443 | this.reset();
444 | return join([
445 | 'while((el = el.parentNode) && el.nodeType == 1){',
446 | body,
447 | '}'
448 | ]);
449 | },
450 |
451 | uses_attr: function(attr) {
452 | if (this.attrs[attr]) return;
453 | this.attrs[attr] = true;
454 | return getAttr(attr);
455 | },
456 | uses_wsattr: function(attr) {
457 | if (this.wsattrs[attr]) return;
458 | this.wsattrs[attr] = true;
459 | return join([this.uses_attr(attr), 'var _WS_'+varForAttr(attr)+' = " "+'+varForAttr(attr)+'+" ";']);
460 | },
461 |
462 | uses_jqueryFilters: function() {
463 | if (this.jqueryFiltersAdded) return;
464 | this.jqueryFiltersAdded = true;
465 | return 'var _$filters = jQuery.find.selectors.filters;';
466 | },
467 |
468 | save: function(lbl) {
469 | return 'var el'+lbl+' = el;';
470 | },
471 | restore: function(lbl) {
472 | this.reset();
473 | return 'el = el'+lbl+';';
474 | }
475 | });
476 |
477 | /**** PSEUDO-CLASS DETAILS ****/
478 |
479 | var pseudoclschecks = {
480 | 'first-child': join([
481 | 'var cel = el;',
482 | 'while(cel = cel.previousSibling){ if (cel.nodeType === 1) BAD; }'
483 | ]),
484 | 'last-child': join([
485 | 'var cel = el;',
486 | 'while(cel = cel.nextSibling){ if (cel.nodeType === 1) BAD; }'
487 | ]),
488 | 'nth-child': function(a,b) {
489 | var get_i = join([
490 | 'var i = 1, cel = el;',
491 | 'while(cel = cel.previousSibling){',
492 | 'if (cel.nodeType === 1) i++;',
493 | '}'
494 | ]);
495 |
496 | if (a == 0) return join([
497 | get_i,
498 | 'if (i- '+b+' != 0) BAD;'
499 | ]);
500 | else if (b == 0 && a >= 0) return join([
501 | get_i,
502 | 'if (i%'+a+' != 0 || i/'+a+' < 0) BAD;'
503 | ]);
504 | else if (b == 0 && a < 0) return join([
505 | 'BAD;'
506 | ]);
507 | else return join([
508 | get_i,
509 | 'if ((i- '+b+')%'+a+' != 0 || (i- '+b+')/'+a+' < 0) BAD;'
510 | ]);
511 | }
512 | };
513 |
514 | // Needs to refence contents of object, so must be injected after definition
515 | pseudoclschecks['only-child'] = join([
516 | pseudoclschecks['first-child'],
517 | pseudoclschecks['last-child']
518 | ]);
519 |
520 | /**** SimpleSelector ****/
521 |
522 | $.selector.SimpleSelector.addMethod('compile', function(el) {
523 | var js = [];
524 |
525 | /* Check against element name */
526 | if (this.tag && this.tag != '*') {
527 | js[js.length] = 'if (el.tagName != "'+this.tag.toUpperCase()+'") BAD;';
528 | }
529 |
530 | /* Check against ID */
531 | if (this.id) {
532 | js[js.length] = el.uses_attr('id');
533 | js[js.length] = 'if (_id !== "'+this.id+'") BAD;';
534 | }
535 |
536 | /* Build className checking variable */
537 | if (this.classes.length) {
538 | js[js.length] = el.uses_wsattr('class');
539 |
540 | /* Check against class names */
541 | $.each(this.classes, function(i, cls){
542 | js[js.length] = 'if (_WS__class.indexOf(" '+cls+' ") == -1) BAD;';
543 | });
544 | }
545 |
546 | /* Check against attributes */
547 | $.each(this.attrs, function(i, attr){
548 | js[js.length] = (attr[1] == '~=') ? el.uses_wsattr(attr[0]) : el.uses_attr(attr[0]);
549 | var check = attrchecks[ attr[1] || '-' ];
550 | check = check.replace( /K/g, varForAttr(attr[0])).replace( /V/g, attr[2] && attr[2].match(STARTS_WITH_QUOTES) ? attr[2].slice(1,-1) : attr[2] );
551 | js[js.length] = 'if ('+check+') BAD;';
552 | });
553 |
554 | /* Check against nots */
555 | $.each(this.nots, function(i, not){
556 | var lbl = ++lbl_id;
557 | var func = join([
558 | 'l'+lbl+':{',
559 | not.compile(el).replace(BAD, 'break l'+lbl).replace(GOOD, 'BAD'),
560 | '}'
561 | ]);
562 |
563 | if (!(not instanceof $.selector.SimpleSelector)) func = join([
564 | el.save(lbl),
565 | func,
566 | el.restore(lbl)
567 | ]);
568 |
569 | js[js.length] = func;
570 | });
571 |
572 | /* Check against pseudo-classes */
573 | $.each(this.pseudo_classes, function(i, pscls){
574 | var check = pseudoclschecks[pscls[0]];
575 | if (check) {
576 | js[js.length] = ( typeof check == 'function' ? check.apply(this, pscls[1]) : check );
577 | }
578 | else if (check = $.find.selectors.filters[pscls[0]]) {
579 | js[js.length] = el.uses_jqueryFilters();
580 | js[js.length] = 'if (!_$filters.'+pscls[0]+'(el)) BAD;';
581 | }
582 | });
583 |
584 | js[js.length] = 'GOOD';
585 |
586 | /* Pass */
587 | return join(js);
588 | });
589 |
590 | var lbl_id = 0;
591 | /** Turns an compiled fragment into the first part of a combination */
592 | function as_subexpr(f) {
593 | if (f.complex)
594 | return join([
595 | 'l'+(++lbl_id)+':{',
596 | f.replace(GOOD, 'break l'+lbl_id),
597 | '}'
598 | ]);
599 | else
600 | return f.replace(GOOD, '');
601 | }
602 |
603 | var combines = {
604 | ' ': function(el, f1, f2) {
605 | return join_complex([
606 | f2,
607 | 'while(true){',
608 | el.parent(),
609 | 'if (!el || el.nodeType !== 1) BAD;',
610 | f1.compile(el).replace(BAD, 'continue'),
611 | '}'
612 | ]);
613 | },
614 |
615 | '>': function(el, f1, f2) {
616 | return join([
617 | f2,
618 | el.parent(),
619 | 'if (!el || el.nodeType !== 1) BAD;',
620 | f1.compile(el)
621 | ]);
622 | },
623 |
624 | '~': function(el, f1, f2) {
625 | return join_complex([
626 | f2,
627 | el.prevLoop(),
628 | f1.compile(el).replace(BAD, 'continue'),
629 | '}',
630 | 'BAD;'
631 | ]);
632 | },
633 |
634 | '+': function(el, f1, f2) {
635 | return join([
636 | f2,
637 | el.prev(),
638 | 'if (!el) BAD;',
639 | f1.compile(el)
640 | ]);
641 | }
642 | };
643 |
644 | $.selector.Selector.addMethod('compile', function(el) {
645 | var l = this.parts.length;
646 |
647 | var expr = this.parts[--l].compile(el);
648 | while (l) {
649 | var combinator = this.parts[--l];
650 | expr = combines[combinator](el, this.parts[--l], as_subexpr(expr));
651 | }
652 |
653 | return expr;
654 | });
655 |
656 | $.selector.SelectorsGroup.addMethod('compile', function(el) {
657 | var expr = [], lbl = ++lbl_id;
658 |
659 | for (var i=0; i < this.parts.length; i++) {
660 | expr[expr.length] = join([
661 | i == 0 ? el.save(lbl) : el.restore(lbl),
662 | 'l'+lbl+'_'+i+':{',
663 | this.parts[i].compile(el).replace(BAD, 'break l'+lbl+'_'+i),
664 | '}'
665 | ]);
666 | }
667 |
668 | expr[expr.length] = 'BAD;';
669 | return join(expr);
670 | });
671 |
672 | $.selector.SelectorBase.addMethod('matches', function(el){
673 | this.matches = new Function('el', join([
674 | 'if (!el) return false;',
675 | this.compile(new State()).replace(BAD, 'return false').replace(GOOD, 'return true')
676 | ]));
677 | return this.matches(el);
678 | });
679 |
680 | })(jQuery);
681 |
682 | ;
683 |
684 |
685 | /* src/jquery.selector.affectedby.js */
686 |
687 | (function($) {
688 |
689 | // TODO:
690 | // Make attributes & IDs work
691 |
692 | var DIRECT = /DIRECT/g;
693 | var CONTEXT = /CONTEXT/g;
694 | var EITHER = /DIRECT|CONTEXT/g;
695 |
696 | $.selector.SelectorBase.addMethod('affectedBy', function(props) {
697 | this.affectedBy = new Function('props', ([
698 | 'var direct_classes, context_classes, direct_attrs, context_attrs, t;',
699 | this.ABC_compile().replace(DIRECT, 'direct').replace(CONTEXT, 'context'),
700 | 'return {classes: {context: context_classes, direct: direct_classes}, attrs: {context: context_attrs, direct: direct_attrs}};'
701 | ]).join("\n"));
702 |
703 | // DEBUG: Print out the compiled funciton
704 | // console.log(this.selector, ''+this.affectedBy);
705 |
706 | return this.affectedBy(props);
707 | });
708 |
709 | $.selector.SimpleSelector.addMethod('ABC_compile', function() {
710 | var parts = [];
711 |
712 | $.each(this.classes, function(i, cls){
713 | parts[parts.length] = "if (t = props.classes['"+cls+"']) (DIRECT_classes || (DIRECT_classes = {}))['"+cls+"'] = t;";
714 | });
715 |
716 | $.each(this.nots, function(i, not){
717 | parts[parts.length] = not.ABC_compile();
718 | });
719 |
720 | return parts.join("\n");
721 | });
722 |
723 | $.selector.Selector.addMethod('ABC_compile', function(arg){
724 | var parts = [];
725 | var i = this.parts.length-1;
726 |
727 | parts[parts.length] = this.parts[i].ABC_compile();
728 | while ((i = i - 2) >= 0) parts[parts.length] = this.parts[i].ABC_compile().replace(EITHER, 'CONTEXT');
729 |
730 | return parts.join("\n");
731 | });
732 |
733 | $.selector.SelectorsGroup.addMethod('ABC_compile', function(){
734 | var parts = [];
735 |
736 | $.each(this.parts, function(i,part){
737 | parts[parts.length] = part.ABC_compile();
738 | });
739 |
740 | return parts.join("\n");
741 | });
742 |
743 |
744 | })(jQuery);
745 | ;
746 |
747 |
748 | /* src/jquery.focusinout.js */
749 |
750 | (function($){
751 |
752 | /**
753 | * Add focusin and focusout support to bind and live for browers other than IE. Designed to be usable in a delegated fashion (like $.live)
754 | * Copyright (c) 2007 Jörn Zaefferer
755 | */
756 | if ($.support.focusinBubbles === undefined) {
757 | $.support.focusinBubbles = !!($.browser) && !$.browser.msie;
758 | }
759 |
760 | if (!$.support.focusinBubbles && !$.event.special.focusin) {
761 | // Emulate focusin and focusout by binding focus and blur in capturing mode
762 | $.each({focus: 'focusin', blur: 'focusout'}, function(original, fix){
763 | $.event.special[fix] = {
764 | setup: function(){
765 | if (!this.addEventListener) return false;
766 | this.addEventListener(original, $.event.special[fix].handler, true);
767 | },
768 | teardown: function(){
769 | if (!this.removeEventListener) return false;
770 | this.removeEventListener(original, $.event.special[fix].handler, true);
771 | },
772 | handler: function(e){
773 | arguments[0] = $.event.fix(e);
774 | arguments[0].type = fix;
775 | return $.event.handle.apply(this, arguments);
776 | }
777 | };
778 | });
779 | }
780 |
781 | (function(){
782 | //IE has some trouble with focusout with select and keyboard navigation
783 | var activeFocus = null;
784 |
785 | $(document)
786 | .bind('focusin', function(e){
787 | var target = e.realTarget || e.target;
788 | if (activeFocus && activeFocus !== target) {
789 | e.type = 'focusout';
790 | $(activeFocus).trigger(e);
791 | e.type = 'focusin';
792 | e.target = target;
793 | }
794 | activeFocus = target;
795 | })
796 | .bind('focusout', function(e){
797 | activeFocus = null;
798 | });
799 | })();
800 |
801 | })(jQuery);
802 | ;
803 |
804 |
805 | /* src/jquery.entwine.js */
806 |
807 | try {
808 | console.log;
809 | }
810 | catch (e) {
811 | window.console = undefined;
812 | }
813 |
814 | (function($) {
815 |
816 | /* Create a subclass of the jQuery object. This was introduced in jQuery 1.5, but removed again in 1.9 */
817 | var sub = function() {
818 | function jQuerySub( selector, context ) {
819 | return new jQuerySub.fn.init( selector, context );
820 | }
821 |
822 | jQuery.extend( true, jQuerySub, $ );
823 | jQuerySub.superclass = $;
824 | jQuerySub.fn = jQuerySub.prototype = $();
825 | jQuerySub.fn.constructor = jQuerySub;
826 | jQuerySub.fn.init = function init( selector, context ) {
827 | var instance = jQuery.fn.init.call( this, selector, context, rootjQuerySub );
828 | return instance instanceof jQuerySub ?
829 | instance :
830 | jQuerySub( instance );
831 | };
832 | jQuerySub.fn.init.prototype = jQuerySub.fn;
833 | var rootjQuerySub = jQuerySub(document);
834 | return jQuerySub;
835 | };
836 |
837 | var namespaces = {};
838 |
839 | $.entwine = function() {
840 | $.fn.entwine.apply(null, arguments);
841 | };
842 |
843 | /**
844 | * A couple of utility functions for accessing the store outside of this closure, and for making things
845 | * operate in a little more easy-to-test manner
846 | */
847 | $.extend($.entwine, {
848 | /**
849 | * Get all the namespaces. Useful for introspection? Internal interface of Namespace not guaranteed consistant
850 | */
851 | namespaces: namespaces,
852 |
853 | /**
854 | * Remove all entwine rules
855 | */
856 | clear_all_rules: function() {
857 | // Remove proxy functions
858 | for (var k in $.fn) { if ($.fn[k].isentwinemethod) delete $.fn[k]; }
859 | // Remove bound events - TODO: Make this pluggable, so this code can be moved to jquery.entwine.events.js
860 | $(document).unbind('.entwine');
861 | $(window).unbind('.entwine');
862 | // Remove namespaces, and start over again
863 | for (var k in namespaces) delete namespaces[k];
864 | for (var k in $.entwine.capture_bindings) delete $.entwine.capture_bindings[k];
865 | },
866 |
867 | WARN_LEVEL_NONE: 0,
868 | WARN_LEVEL_IMPORTANT: 1,
869 | WARN_LEVEL_BESTPRACTISE: 2,
870 |
871 | /**
872 | * Warning level. Set to a higher level to get warnings dumped to console.
873 | */
874 | warningLevel: 0,
875 |
876 | /** Utility to optionally display warning messages depending on level */
877 | warn: function(message, level) {
878 | if (level <= $.entwine.warningLevel && console && console.warn) {
879 | console.warn(message);
880 | if (console.trace) console.trace();
881 | }
882 | },
883 |
884 | warn_exception: function(where, /* optional: */ on, e) {
885 | if ($.entwine.WARN_LEVEL_IMPORTANT <= $.entwine.warningLevel && console && console.warn) {
886 | if (arguments.length == 2) { e = on; on = null; }
887 |
888 | if (on) console.warn('Uncaught exception',e,'in',where,'on',on);
889 | else console.warn('Uncaught exception',e,'in',where);
890 |
891 | if (e.stack) console.warn("Stack Trace:\n" + e.stack);
892 | }
893 | }
894 | });
895 |
896 |
897 | /** Stores a count of definitions, so that we can sort identical selectors by definition order */
898 | var rulecount = 0;
899 |
900 | var Rule = Base.extend({
901 | init: function(selector, name) {
902 | this.selector = selector;
903 | this.specifity = selector.specifity();
904 | this.important = 0;
905 | this.name = name;
906 | this.rulecount = rulecount++;
907 | }
908 | });
909 |
910 | Rule.compare = function(a, b) {
911 | var as = a.specifity, bs = b.specifity;
912 |
913 | return (a.important - b.important) ||
914 | (as[0] - bs[0]) ||
915 | (as[1] - bs[1]) ||
916 | (as[2] - bs[2]) ||
917 | (a.rulecount - b.rulecount) ;
918 | };
919 |
920 | $.entwine.RuleList = function() {
921 | var list = [];
922 |
923 | list.addRule = function(selector, name){
924 | var rule = Rule(selector, name);
925 |
926 | list[list.length] = rule;
927 | list.sort(Rule.compare);
928 |
929 | return rule;
930 | };
931 |
932 | return list;
933 | };
934 |
935 | var handlers = [];
936 |
937 | /**
938 | * A Namespace holds all the information needed for adding entwine methods to a namespace (including the _null_ namespace)
939 | */
940 | $.entwine.Namespace = Base.extend({
941 | init: function(name){
942 | if (name && !name.match(/^[A-Za-z0-9.]+$/)) $.entwine.warn('Entwine namespace '+name+' is not formatted as period seperated identifiers', $.entwine.WARN_LEVEL_BESTPRACTISE);
943 | name = name || '__base';
944 |
945 | this.name = name;
946 | this.store = {};
947 |
948 | namespaces[name] = this;
949 |
950 | if (name == "__base") {
951 | this.injectee = $.fn;
952 | this.$ = $;
953 | }
954 | else {
955 | // We're in a namespace, so we build a Class that subclasses the jQuery Object Class to inject namespace functions into
956 | this.$ = $.sub ? $.sub() : sub();
957 | // Work around bug in sub() - subclass must share cache with root or data won't get cleared by cleanData
958 | this.$.cache = $.cache;
959 |
960 | this.injectee = this.$.prototype;
961 |
962 | // We override entwine to inject the name of this namespace when defining blocks inside this namespace
963 | var entwine_wrapper = this.injectee.entwine = function(spacename) {
964 | var args = arguments;
965 |
966 | if (!spacename || typeof spacename != 'string') { args = $.makeArray(args); args.unshift(name); }
967 | else if (spacename.charAt(0) != '.') args[0] = name+'.'+spacename;
968 |
969 | return $.fn.entwine.apply(this, args);
970 | };
971 |
972 | this.$.entwine = function() {
973 | entwine_wrapper.apply(null, arguments);
974 | };
975 |
976 | for (var i = 0; i < handlers.length; i++) {
977 | var handler = handlers[i], builder;
978 |
979 | // Inject jQuery object method overrides
980 | if (builder = handler.namespaceMethodOverrides) {
981 | var overrides = builder(this);
982 | for (var k in overrides) this.injectee[k] = overrides[k];
983 | }
984 |
985 | // Inject $.entwine function overrides
986 | if (builder = handler.namespaceStaticOverrides) {
987 | var overrides = builder(this);
988 | for (var k in overrides) this.$.entwine[k] = overrides[k];
989 | }
990 | }
991 | }
992 | },
993 |
994 | /**
995 | * Returns a function that does selector matching against the function list for a function name
996 | * Used by proxy for all calls, and by ctorProxy to handle _super calls
997 | * @param {String} name - name of the function as passed in the construction object
998 | * @param {String} funcprop - the property on the Rule object that gives the actual function to call
999 | * @param {function} basefunc - the non-entwine function to use as the catch-all function at the bottom of the stack
1000 | */
1001 | one: function(name, funcprop, basefunc) {
1002 | var namespace = this;
1003 | var funcs = this.store[name];
1004 |
1005 | var one = function(el, args, i){
1006 | if (i === undefined) i = funcs.length;
1007 | while (i--) {
1008 | if (funcs[i].selector.matches(el)) {
1009 | var ret, tmp_i = el.i, tmp_f = el.f;
1010 | el.i = i; el.f = one;
1011 | try { ret = funcs[i][funcprop].apply(namespace.$(el), args); }
1012 | finally { el.i = tmp_i; el.f = tmp_f; }
1013 | return ret;
1014 | }
1015 | }
1016 | // If we didn't find a entwine-defined function, but there is a non-entwine function to use as a base, try that
1017 | if (basefunc) return basefunc.apply(namespace.$(el), args);
1018 | };
1019 |
1020 | return one;
1021 | },
1022 |
1023 | /**
1024 | * A proxy is a function attached to a callable object (either the base jQuery.fn or a subspace object) which handles
1025 | * finding and calling the correct function for each member of the current jQuery context
1026 | * @param {String} name - name of the function as passed in the construction object
1027 | * @param {function} basefunc - the non-entwine function to use as the catch-all function at the bottom of the stack
1028 | */
1029 | build_proxy: function(name, basefunc) {
1030 | var one = this.one(name, 'func', basefunc);
1031 |
1032 | var prxy = function() {
1033 | var rv, ctx = $(this);
1034 |
1035 | var i = ctx.length;
1036 | while (i--) rv = one(ctx[i], arguments);
1037 | return rv;
1038 | };
1039 |
1040 | return prxy;
1041 | },
1042 |
1043 | bind_proxy: function(selector, name, func) {
1044 | var rulelist = this.store[name] || (this.store[name] = $.entwine.RuleList());
1045 |
1046 | var rule = rulelist.addRule(selector, name); rule.func = func;
1047 |
1048 | if (!this.injectee.hasOwnProperty(name) || !this.injectee[name].isentwinemethod) {
1049 | this.injectee[name] = this.build_proxy(name, this.injectee.hasOwnProperty(name) ? this.injectee[name] : null);
1050 | this.injectee[name].isentwinemethod = true;
1051 | }
1052 |
1053 | if (!this.injectee[name].isentwinemethod) {
1054 | $.entwine.warn('Warning: Entwine function '+name+' clashes with regular jQuery function - entwine function will not be callable directly on jQuery object', $.entwine.WARN_LEVEL_IMPORTANT);
1055 | }
1056 | },
1057 |
1058 | add: function(selector, data) {
1059 | // For every item in the hash, try ever method handler, until one returns true
1060 | for (var k in data) {
1061 | var v = data[k];
1062 |
1063 | for (var i = 0; i < handlers.length; i++) {
1064 | if (handlers[i].bind && handlers[i].bind.call(this, selector, k, v)) break;
1065 | }
1066 | }
1067 | },
1068 |
1069 | has: function(ctx, name) {
1070 | var rulelist = this.store[name];
1071 | if (!rulelist) return false;
1072 |
1073 | /* We go forward this time, since low specifity is likely to knock out a bunch of elements quickly */
1074 | for (var i = 0 ; i < rulelist.length; i++) {
1075 | ctx = ctx.not(rulelist[i].selector);
1076 | if (!ctx.length) return true;
1077 | }
1078 | return false;
1079 | }
1080 | });
1081 |
1082 | /**
1083 | * A handler is some javascript code that adds support for some time of key / value pair passed in the hash to the Namespace add method.
1084 | * The default handlers provided (and included by default) are event, ctor and properties
1085 | */
1086 | $.entwine.Namespace.addHandler = function(handler) {
1087 | for (var i = 0; i < handlers.length && handlers[i].order < handler.order; i++) { /* Pass */ }
1088 | handlers.splice(i, 0, handler);
1089 | };
1090 |
1091 | $.entwine.Namespace.addHandler({
1092 | order: 50,
1093 |
1094 | bind: function(selector, k, v){
1095 | if ($.isFunction(v)) {
1096 | this.bind_proxy(selector, k, v);
1097 | return true;
1098 | }
1099 | }
1100 | });
1101 |
1102 | $.extend($.fn, {
1103 | /**
1104 | * Main entwine function. Used for new definitions, calling into a namespace (or forcing the base namespace) and entering a using block
1105 | *
1106 | */
1107 | entwine: function(spacename) {
1108 | var i = 0;
1109 | /* Don't actually work out selector until we try and define something on it - we might be opening a namespace on an function-traveresed object
1110 | which have non-standard selectors like .parents(.foo).slice(0,1) */
1111 | var selector = null;
1112 |
1113 | /* By default we operator on the base namespace */
1114 | var namespace = namespaces.__base || $.entwine.Namespace();
1115 |
1116 | /* If the first argument is a string, then it's the name of a namespace. Look it up */
1117 | if (typeof spacename == 'string') {
1118 | if (spacename.charAt('0') == '.') spacename = spacename.substr(1);
1119 | if (spacename) namespace = namespaces[spacename] || $.entwine.Namespace(spacename);
1120 | i=1;
1121 | }
1122 |
1123 | /* All remaining arguments should either be using blocks or definition hashs */
1124 | while (i < arguments.length) {
1125 | var res = arguments[i++];
1126 |
1127 | // If it's a function, call it - either it's a using block or it's a namespaced entwine definition
1128 | if ($.isFunction(res)) {
1129 | if (res.length != 1) $.entwine.warn('Function block inside entwine definition does not take $ argument properly', $.entwine.WARN_LEVEL_IMPORTANT);
1130 | res = res.call(namespace.$(this), namespace.$);
1131 | }
1132 |
1133 | // If we have a entwine definition hash, inject it into namespace
1134 | if (res) {
1135 | if (selector === null) selector = this.selector ? $.selector(this.selector) : false;
1136 |
1137 | if (selector) namespace.add(selector, res);
1138 | else $.entwine.warn('Entwine block given to entwine call without selector. Make sure you call $(selector).entwine when defining blocks', $.entwine.WARN_LEVEL_IMPORTANT);
1139 | }
1140 | }
1141 |
1142 | /* Finally, return the jQuery object 'this' refers to, wrapped in the new namespace */
1143 | return namespace.$(this);
1144 | },
1145 |
1146 | /**
1147 | * Calls the next most specific version of the current entwine method
1148 | */
1149 | _super: function(){
1150 | var rv, i = this.length;
1151 | while (i--) {
1152 | var el = this[0];
1153 | rv = el.f(el, arguments, el.i);
1154 | }
1155 | return rv;
1156 | }
1157 | });
1158 |
1159 | })(jQuery);
1160 | ;
1161 |
1162 |
1163 | /* src/domevents/jquery.entwine.domevents.addrem.js */
1164 |
1165 | (function($){
1166 |
1167 | // Gets all the child elements of a particular elements, stores it in an array
1168 | function getElements(store, original) {
1169 | var node, i = store.length, next = (typeof original.firstChild == 'undefined' ? $(original)[0].firstChild : original);
1170 |
1171 | while ((node = next)) {
1172 | if (node.nodeType === 1) store[i++] = node;
1173 | next = node.firstChild || node.nextSibling;
1174 | while (!next && (node = node.parentNode) && node !== original) next = node.nextSibling;
1175 | }
1176 | }
1177 |
1178 | // This might be faster? Or slower? @todo: benchmark.
1179 | function getElementsAlt(store, node) {
1180 | if (node.getElementsByTagName) {
1181 | var els = node.getElementsByTagName('*'), len = els.length, i = 0, j = store.length;
1182 | for(; i < len; i++, j++) {
1183 | store[j] = els[i];
1184 | }
1185 | }
1186 | else if (node.childNodes) {
1187 | var els = node.childNodes, len = els.length, i = 0;
1188 | for(; i < len; i++) {
1189 | getElements(store, els[i]);
1190 | }
1191 | }
1192 | }
1193 |
1194 | var isArrayLike = function(obj){
1195 | // The `in` check is from jQuery’s fix for an iOS 8 64-bit JIT object length bug:
1196 | // https://github.com/jquery/jquery/pull/2185
1197 | // When passing a non-object (e.g. boolean) can.each fails where it previously did nothing.
1198 | // https://github.com/canjs/canjs/issues/1989
1199 | var length = obj && typeof obj !== 'boolean' &&
1200 | typeof obj !== 'number' &&
1201 | "length" in obj && obj.length;
1202 |
1203 | // var length = "length" in obj && obj.length;
1204 | return typeof arr !== "function" &&
1205 | ( length === 0 || typeof length === "number" && length > 0 && ( length - 1 ) in obj );
1206 | };
1207 |
1208 | var dontTrigger = false;
1209 |
1210 | var version = $.prototype.jquery.split('.');
1211 |
1212 | // @TODO Modified by Ed to make compatible with jQuery 3.6.0
1213 | // Monkey patch dom manipulation to catch all regular jQuery add element calls
1214 | ['after', 'prepend', 'before', 'append','replaceWith'].forEach(function (name) {
1215 | var original = $.fn[name];
1216 | $.fn[name] = function (elem) {
1217 | var added = [];
1218 |
1219 | if (!dontTrigger) {
1220 | // @TODO Modified by Ed to make compatible with jQuery 3.6.0
1221 | if (elem.each) {
1222 | elem.each(function (key, _elem) {
1223 | if (_elem.nodeType == 1) added[added.length] = _elem;
1224 | getElements(added, _elem);
1225 | });
1226 | } else {
1227 | if (elem.nodeType == 1) added[added.length] = elem;
1228 | getElements(added, elem);
1229 | }
1230 | }
1231 |
1232 | var rv = original.apply(this, arguments);
1233 |
1234 | if (!dontTrigger && added.length) {
1235 | var event = $.Event('EntwineElementsAdded');
1236 | event.targets = added;
1237 | $(document).triggerHandler(event);
1238 | }
1239 |
1240 | return rv;
1241 | };
1242 | });
1243 |
1244 | // Monkey patch $.fn.html to catch when jQuery sets innerHTML directly
1245 | var _html = $.prototype.html;
1246 | $.prototype.html = function(value) {
1247 | if (value === undefined) return _html.apply(this, arguments);
1248 |
1249 | dontTrigger = true;
1250 | var res = _html.apply(this, arguments);
1251 | dontTrigger = false;
1252 |
1253 | var added = [];
1254 |
1255 | var i = 0, length = this.length;
1256 | for (; i < length; i++ ) getElements(added, this[i]);
1257 |
1258 | var event = $.Event('EntwineElementsAdded');
1259 | event.targets = added;
1260 | $(document).triggerHandler(event);
1261 |
1262 | return res;
1263 | }
1264 |
1265 | // If this is true, we've changed something to call cleanData so that we can catch the elements, but we don't
1266 | // want to call the underlying original $.cleanData
1267 | var supressActualClean = false;
1268 |
1269 | // Monkey patch $.cleanData to catch element removal
1270 | var _cleanData = $.cleanData;
1271 | $.cleanData = function( elems ) {
1272 | // By default we can assume all elements passed are legitimately being removeed
1273 | var removed = elems;
1274 |
1275 | // Except if we're supressing actual clean - we might be being called by jQuery "being careful" about detaching nodes
1276 | // before attaching them. So we need to check to make sure these nodes currently are in a document
1277 | if (supressActualClean) {
1278 | var i = 0, len = elems.length, removed = [], ri = 0;
1279 | for(; i < len; i++) {
1280 | var node = elems[i], current = node;
1281 | while (current = current.parentNode) {
1282 | if (current.nodeType == 9) { removed[ri++] = node; break; }
1283 | }
1284 | }
1285 | }
1286 |
1287 | if (removed.length) {
1288 | var event = $.Event('EntwineElementsRemoved');
1289 | event.targets = removed;
1290 | $(document).triggerHandler(event);
1291 | }
1292 |
1293 | if (!supressActualClean) _cleanData.apply(this, arguments);
1294 | }
1295 |
1296 | // Monkey patch $.fn.remove to catch when we're just detaching (keepdata == 1) -
1297 | // this doesn't call cleanData but still needs to trigger event
1298 | var _remove = $.prototype.remove;
1299 | $.prototype.remove = function(selector, keepdata) {
1300 | supressActualClean = keepdata;
1301 | var rv = _remove.call(this, selector);
1302 | supressActualClean = false;
1303 | return rv;
1304 | }
1305 |
1306 | // And on DOM ready, trigger adding once
1307 | $(function(){
1308 | var added = []; getElements(added, document);
1309 |
1310 | var event = $.Event('EntwineElementsAdded');
1311 | event.targets = added;
1312 | $(document).triggerHandler(event);
1313 | });
1314 |
1315 |
1316 | })(jQuery);;
1317 |
1318 |
1319 | /* src/domevents/jquery.entwine.domevents.maybechanged.js */
1320 |
1321 | (function($){
1322 |
1323 | /** Utility function to monkey-patch a jQuery method */
1324 | var monkey = function( /* method, method, ...., patch */){
1325 | var methods = $.makeArray(arguments);
1326 | var patch = methods.pop();
1327 |
1328 | $.each(methods, function(i, method){
1329 | var old = $.fn[method];
1330 |
1331 | $.fn[method] = function() {
1332 | var self = this, args = $.makeArray(arguments);
1333 |
1334 | var rv = old.apply(self, args);
1335 | patch.apply(self, args);
1336 | return rv;
1337 | }
1338 | });
1339 | }
1340 |
1341 | /** What to call to run a function 'soon'. Normally setTimeout, but for syncronous mode we override so soon === now */
1342 | var runSoon = window.setTimeout;
1343 |
1344 | /** The timer handle for the asyncronous matching call */
1345 | var ChangeDetails = Base.extend({
1346 |
1347 | init: function() {
1348 | this.global = false;
1349 | this.attrs = {};
1350 | this.classes = {};
1351 | },
1352 |
1353 | /** Fire the change event. Only fires on the document node, so bind to that */
1354 | triggerEvent: function() {
1355 | // If we're not the active changes instance any more, don't trigger
1356 | if (changes != this) return;
1357 |
1358 | // Cancel any pending timeout (if we're directly called in the mean time)
1359 | if (this.check_id) clearTimeout(this.check_id);
1360 |
1361 | // Reset the global changes object to be a new instance (do before trigger, in case trigger fires changes itself)
1362 | changes = new ChangeDetails();
1363 |
1364 | // Fire event
1365 | $(document).triggerHandler("EntwineSubtreeMaybeChanged", [this]);
1366 | },
1367 |
1368 | changed: function() {
1369 | if (!this.check_id) {
1370 | var self = this;
1371 | this.check_id = runSoon(function(){ self.check_id = null; self.triggerEvent(); }, 10);
1372 | }
1373 | },
1374 |
1375 | addAll: function() {
1376 | if (this.global) return this; // If we've already flagged as a global change, just skip
1377 |
1378 | this.global = true;
1379 | this.changed();
1380 | return this;
1381 | },
1382 |
1383 | addSubtree: function(node) {
1384 | return this.addAll();
1385 | },
1386 |
1387 | /* For now we don't do this. It's expensive, and jquery.entwine.ctors doesn't use this information anyway */
1388 | addSubtreeFuture: function(node) {
1389 | if (this.global) return this; // If we've already flagged as a global change, just skip
1390 |
1391 | this.subtree = this.subtree ? this.subtree.add(node) : $(node);
1392 | this.changed();
1393 | return this;
1394 | },
1395 |
1396 | addAttr: function(attr, node) {
1397 | if (this.global) return this;
1398 |
1399 | this.attrs[attr] = (attr in this.attrs) ? this.attrs[attr].add(node) : $(node);
1400 | this.changed();
1401 | return this;
1402 | },
1403 |
1404 | addClass: function(klass, node) {
1405 | if (this.global) return this;
1406 |
1407 | this.classes[klass] = (klass in this.classes) ? this.classes[klass].add(node) : $(node);
1408 | this.changed();
1409 | return this;
1410 | }
1411 | });
1412 |
1413 | var changes = new ChangeDetails();
1414 |
1415 | // Element add events trigger maybechanged events
1416 |
1417 | $(document).bind('EntwineElementsAdded', function(e){ changes.addSubtree(e.targets); });
1418 |
1419 | // Element remove events trigger maybechanged events, but we have to wait until after the nodes are actually removed
1420 | // (EntwineElementsRemoved fires _just before_ the elements are removed so the data still exists), especially in syncronous mode
1421 |
1422 | var removed = null;
1423 | $(document).bind('EntwineElementsRemoved', function(e){ removed = e.targets; });
1424 |
1425 | monkey('remove', 'html', 'empty', function(){
1426 | var subtree = removed; removed = null;
1427 | if (subtree) changes.addSubtree(subtree);
1428 | });
1429 |
1430 | // We also need to know when an attribute, class, etc changes. Patch the relevant jQuery methods here
1431 |
1432 | monkey('removeAttr', function(attr){
1433 | changes.addAttr(attr, this);
1434 | });
1435 |
1436 | monkey('addClass', 'removeClass', 'toggleClass', function(klass){
1437 | if (typeof klass == 'string') changes.addClass(klass, this);
1438 | });
1439 |
1440 | monkey('attr', function(a, b){
1441 | if (b !== undefined && typeof a == 'string') changes.addAttr(a, this);
1442 | else if (typeof a != 'string') { for (var k in a) changes.addAttr(k, this); }
1443 | });
1444 |
1445 | // Add some usefull accessors to $.entwine
1446 |
1447 | $.extend($.entwine, {
1448 | /**
1449 | * Make onmatch and onunmatch work in synchronous mode - that is, new elements will be detected immediately after
1450 | * the DOM manipulation that made them match. This is only really useful for during testing, since it's pretty slow
1451 | * (otherwise we'd make it the default).
1452 | */
1453 | synchronous_mode: function() {
1454 | if (changes && changes.check_id) clearTimeout(changes.check_id);
1455 | changes = new ChangeDetails();
1456 |
1457 | runSoon = function(func, delay){ func.call(this); return null; };
1458 | },
1459 |
1460 | /**
1461 | * Trigger onmatch and onunmatch now - usefull for after DOM manipulation by methods other than through jQuery.
1462 | * Called automatically on document.ready
1463 | */
1464 | triggerMatching: function() {
1465 | changes.addAll();
1466 | }
1467 | });
1468 |
1469 | })(jQuery);;
1470 |
1471 |
1472 | /* src/jquery.entwine.events.js */
1473 |
1474 | (function($) {
1475 |
1476 | /** Taken from jQuery 1.5.2 for backwards compatibility */
1477 | if ($.support.changeBubbles == undefined) {
1478 | $.support.changeBubbles = true;
1479 |
1480 | var el = document.createElement("div");
1481 | eventName = "onchange";
1482 |
1483 | if (el.attachEvent) {
1484 | var isSupported = (eventName in el);
1485 | if (!isSupported) {
1486 | el.setAttribute(eventName, "return;");
1487 | isSupported = typeof el[eventName] === "function";
1488 | }
1489 |
1490 | $.support.changeBubbles = isSupported;
1491 | }
1492 | }
1493 |
1494 | /* Return true if node b is the same as, or is a descendant of, node a */
1495 | if (document.compareDocumentPosition) {
1496 | var is_or_contains = function(a, b) {
1497 | return a && b && (a == b || !!(a.compareDocumentPosition(b) & 16));
1498 | };
1499 | }
1500 | else {
1501 | var is_or_contains = function(a, b) {
1502 | return a && b && (a == b || (a.contains ? a.contains(b) : true));
1503 | };
1504 | }
1505 |
1506 | /* Add the methods to handle event binding to the Namespace class */
1507 | $.entwine.Namespace.addMethods({
1508 | build_event_proxy: function(name) {
1509 | var one = this.one(name, 'func');
1510 |
1511 | var prxy = function(e, data) {
1512 | // For events that do not bubble we manually trigger delegation (see delegate_submit below)
1513 | // If this event is a manual trigger, the event we actually want to bubble is attached as a property of the passed event
1514 | e = e.delegatedEvent || e;
1515 |
1516 | var el = e.target;
1517 | while (el && el.nodeType == 1 && !e.isPropagationStopped()) {
1518 | var ret = one(el, arguments);
1519 | if (ret !== undefined) e.result = ret;
1520 | if (ret === false) { e.preventDefault(); e.stopPropagation(); }
1521 |
1522 | el = el.parentNode;
1523 | }
1524 | };
1525 |
1526 | return prxy;
1527 | },
1528 |
1529 | build_mouseenterleave_proxy: function(name) {
1530 | var one = this.one(name, 'func');
1531 |
1532 | var prxy = function(e) {
1533 | var el = e.target;
1534 | var rel = e.relatedTarget;
1535 |
1536 | while (el && el.nodeType == 1 && !e.isPropagationStopped()) {
1537 | /* We know el contained target. If it also contains relatedTarget then we didn't mouseenter / leave. What's more, every ancestor will also
1538 | contan el and rel, and so we can just stop bubbling */
1539 | if (is_or_contains(el, rel)) break;
1540 |
1541 | var ret = one(el, arguments);
1542 | if (ret !== undefined) e.result = ret;
1543 | if (ret === false) { e.preventDefault(); e.stopPropagation(); }
1544 |
1545 | el = el.parentNode;
1546 | }
1547 | };
1548 |
1549 | return prxy;
1550 | },
1551 |
1552 | build_change_proxy: function(name) {
1553 | var one = this.one(name, 'func');
1554 |
1555 | /*
1556 | This change bubble emulation code is taken mostly from jQuery 1.6 - unfortunately we can't easily reuse any of
1557 | it without duplication, so we'll have to re-migrate any bugfixes
1558 | */
1559 |
1560 | // Get the value of an item. Isn't supposed to be interpretable, just stable for some value, and different
1561 | // once the value changes
1562 | var getVal = function( elem ) {
1563 | var type = elem.type, val = elem.value;
1564 |
1565 | if (type === "radio" || type === "checkbox") {
1566 | val = elem.checked;
1567 | }
1568 | else if (type === "select-multiple") {
1569 | val = "";
1570 | if (elem.selectedIndex > -1) {
1571 | val = jQuery.map(elem.options, function(elem){ return elem.selected; }).join("-");
1572 | }
1573 | }
1574 | else if (jQuery.nodeName(elem, "select")) {
1575 | val = elem.selectedIndex;
1576 | }
1577 |
1578 | return val;
1579 | };
1580 |
1581 | // Test if a node name is a form input
1582 | var rformElems = /^(?:textarea|input|select)$/i;
1583 |
1584 | // Check if this event is a change, and bubble the change event if it is
1585 | var testChange = function(e) {
1586 | var elem = e.target, data, val;
1587 |
1588 | if (!rformElems.test(elem.nodeName) || elem.readOnly) return;
1589 |
1590 | data = jQuery.data(elem, "_entwine_change_data");
1591 | val = getVal(elem);
1592 |
1593 | // the current data will be also retrieved by beforeactivate
1594 | if (e.type !== "focusout" || elem.type !== "radio") {
1595 | jQuery.data(elem, "_entwine_change_data", val);
1596 | }
1597 |
1598 | if (data === undefined || val === data) return;
1599 |
1600 | if (data != null || val) {
1601 | e.type = "change";
1602 |
1603 | while (elem && elem.nodeType == 1 && !e.isPropagationStopped()) {
1604 | var ret = one(elem, arguments);
1605 | if (ret !== undefined) e.result = ret;
1606 | if (ret === false) { e.preventDefault(); e.stopPropagation(); }
1607 |
1608 | elem = elem.parentNode;
1609 | }
1610 | }
1611 | };
1612 |
1613 | // The actual proxy - responds to several events, some of which triger a change check, some
1614 | // of which just store the value for future change checks
1615 | var prxy = function(e) {
1616 | var event = e.type, elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : "";
1617 |
1618 | switch (event) {
1619 | case 'focusout':
1620 | case 'beforedeactivate':
1621 | testChange.apply(this, arguments);
1622 | break;
1623 |
1624 | case 'click':
1625 | if ( type === "radio" || type === "checkbox" || jQuery.nodeName( elem, "select" ) ) {
1626 | testChange.apply(this, arguments);
1627 | }
1628 | break;
1629 |
1630 | // Change has to be called before submit
1631 | // Keydown will be called before keypress, which is used in submit-event delegation
1632 | case 'keydown':
1633 | if (
1634 | (e.keyCode === 13 && !jQuery.nodeName( elem, "textarea" ) ) ||
1635 | (e.keyCode === 32 && (type === "checkbox" || type === "radio")) ||
1636 | type === "select-multiple"
1637 | ) {
1638 | testChange.apply(this, arguments);
1639 | }
1640 | break;
1641 |
1642 | // Beforeactivate happens also before the previous element is blurred
1643 | // with this event you can't trigger a change event, but you can store
1644 | // information
1645 | case 'focusin':
1646 | case 'beforeactivate':
1647 | jQuery.data( elem, "_entwine_change_data", getVal(elem) );
1648 | break;
1649 | }
1650 | }
1651 |
1652 | return prxy;
1653 | },
1654 |
1655 | bind_event: function(selector, name, func, event) {
1656 | var funcs = this.store[name] || (this.store[name] = $.entwine.RuleList()) ;
1657 | var proxies = funcs.proxies || (funcs.proxies = {});
1658 |
1659 | var rule = funcs.addRule(selector, name); rule.func = func;
1660 |
1661 | if (!proxies[name]) {
1662 | switch (name) {
1663 | case 'onmouseenter':
1664 | proxies[name] = this.build_mouseenterleave_proxy(name);
1665 | event = 'mouseover';
1666 | break;
1667 | case 'onmouseleave':
1668 | proxies[name] = this.build_mouseenterleave_proxy(name);
1669 | event = 'mouseout';
1670 | break;
1671 | case 'onchange':
1672 | if (!$.support.changeBubbles) {
1673 | proxies[name] = this.build_change_proxy(name);
1674 | event = 'click keydown focusin focusout beforeactivate beforedeactivate';
1675 | }
1676 | break;
1677 | case 'onsubmit':
1678 | event = 'delegatedSubmit';
1679 | break;
1680 | case 'onfocus':
1681 | case 'onblur':
1682 | $.entwine.warn('Event '+event+' not supported - using focusin / focusout instead', $.entwine.WARN_LEVEL_IMPORTANT);
1683 | }
1684 |
1685 | // If none of the special handlers created a proxy, use the generic proxy
1686 | if (!proxies[name]) proxies[name] = this.build_event_proxy(name);
1687 |
1688 | $(document).bind(event.replace(/(\s+|$)/g, '.entwine$1'), proxies[name]);
1689 | }
1690 | }
1691 | });
1692 |
1693 | $.entwine.Namespace.addHandler({
1694 | order: 40,
1695 |
1696 | bind: function(selector, k, v){
1697 | var match, event;
1698 | if ($.isFunction(v) && (match = k.match(/^on(.*)/))) {
1699 | event = match[1];
1700 | this.bind_event(selector, k, v, event);
1701 | return true;
1702 | }
1703 | }
1704 | });
1705 |
1706 | // Find all forms and bind onsubmit to trigger on the document too.
1707 | // This is the only event that can't be grabbed via delegation
1708 |
1709 | var delegate_submit = function(e, data){
1710 | var delegationEvent = $.Event('delegatedSubmit'); delegationEvent.delegatedEvent = e;
1711 | return $(document).trigger(delegationEvent, data);
1712 | };
1713 |
1714 | $(document).bind('EntwineElementsAdded', function(e){
1715 | var forms = $(e.targets).filter('form');
1716 | if (!forms.length) return;
1717 |
1718 | forms.bind('submit.entwine_delegate_submit', delegate_submit);
1719 | });
1720 |
1721 | })(jQuery);
1722 | ;
1723 |
1724 |
1725 | /* src/jquery.entwine.eventcapture.js */
1726 |
1727 | (function($) {
1728 |
1729 | $.entwine.Namespace.addMethods({
1730 | bind_capture: function(selector, event, name, capture) {
1731 | var store = this.captures || (this.captures = {});
1732 | var rulelists = store[event] || (store[event] = {});
1733 | var rulelist = rulelists[name] || (rulelists[name] = $.entwine.RuleList());
1734 |
1735 | rule = rulelist.addRule(selector, event);
1736 | rule.handler = name;
1737 |
1738 | this.bind_proxy(selector, name, capture);
1739 | }
1740 | });
1741 |
1742 | var bindings = $.entwine.capture_bindings = {};
1743 |
1744 | var event_proxy = function(event) {
1745 | return function(e) {
1746 | var namespace, capturelists, forevent, capturelist, rule, handler, sel;
1747 |
1748 | for (var k in $.entwine.namespaces) {
1749 | namespace = $.entwine.namespaces[k];
1750 | capturelists = namespace.captures;
1751 |
1752 | if (capturelists && (forevent = capturelists[event])) {
1753 | for (var k in forevent) {
1754 | var capturelist = forevent[k];
1755 | var triggered = namespace.$([]);
1756 |
1757 | // Stepping through each selector from most to least specific
1758 | var j = capturelist.length;
1759 | while (j--) {
1760 | rule = capturelist[j];
1761 | handler = rule.handler;
1762 | sel = rule.selector.selector;
1763 |
1764 | var matching = namespace.$(sel).not(triggered);
1765 | matching[handler].apply(matching, arguments);
1766 |
1767 | triggered = triggered.add(matching);
1768 | }
1769 | }
1770 | }
1771 | }
1772 | }
1773 | };
1774 |
1775 | var selector_proxy = function(selector, handler, includechildren) {
1776 | var matcher = $.selector(selector);
1777 | return function(e){
1778 | if (matcher.matches(e.target)) return handler.apply(this, arguments);
1779 | }
1780 | };
1781 |
1782 | var document_proxy = function(selector, handler, includechildren) {
1783 | return function(e){
1784 | if (e.target === document) return handler.apply(this, arguments);
1785 | }
1786 | };
1787 |
1788 | var window_proxy = function(selector, handler, includechildren) {
1789 | return function(e){
1790 | if (e.target === window) return handler.apply(this, arguments);
1791 | }
1792 | };
1793 |
1794 | var property_proxy = function(property, handler, includechildren) {
1795 | var matcher;
1796 |
1797 | return function(e){
1798 | var match = this['get'+property]();
1799 |
1800 | if (typeof(match) == 'string') {
1801 | var matcher = (matcher && match == matcher.selector) ? matcher : $.selector(match);
1802 | if (matcher.matches(e.target)) return handler.apply(this, arguments);
1803 | }
1804 | else {
1805 | if ($.inArray(e.target, match) !== -1) return handler.apply(this, arguments);
1806 | }
1807 | }
1808 | };
1809 |
1810 | $.entwine.Namespace.addHandler({
1811 | order: 10,
1812 |
1813 | bind: function(selector, k, v) {
1814 | var match;
1815 | if ($.isPlainObject(v) && (match = k.match(/^from\s*(.*)/))) {
1816 | var from = match[1];
1817 | var proxyGen;
1818 |
1819 | if (from.match(/[^\w]/)) proxyGen = selector_proxy;
1820 | else if (from == 'Window' || from == 'window') proxyGen = window_proxy;
1821 | else if (from == 'Document' || from == 'document') proxyGen = document_proxy;
1822 | else proxyGen = property_proxy;
1823 |
1824 | for (var onevent in v) {
1825 | var handler = v[onevent];
1826 | match = onevent.match(/^on(.*)/);
1827 | var event = match[1];
1828 |
1829 | this.bind_capture(selector, event, k + '_' + event, proxyGen(from, handler));
1830 |
1831 | if (!bindings[event]) {
1832 | var namespaced = event.replace(/(\s+|$)/g, '.entwine$1');
1833 | bindings[event] = event_proxy(event);
1834 |
1835 | $(proxyGen == window_proxy ? window : document).bind(namespaced, bindings[event]);
1836 | }
1837 | }
1838 |
1839 | return true;
1840 | }
1841 | }
1842 | });
1843 |
1844 | })(jQuery);
1845 | ;
1846 |
1847 |
1848 | /* src/jquery.entwine.ctors.js */
1849 |
1850 | (function($) {
1851 |
1852 | /* Add the methods to handle constructor & destructor binding to the Namespace class */
1853 | $.entwine.Namespace.addMethods({
1854 | bind_condesc: function(selector, name, func) {
1855 | var ctors = this.store.ctors || (this.store.ctors = $.entwine.RuleList()) ;
1856 |
1857 | var rule;
1858 | for (var i = 0 ; i < ctors.length; i++) {
1859 | if (ctors[i].selector.selector == selector.selector) {
1860 | rule = ctors[i]; break;
1861 | }
1862 | }
1863 | if (!rule) {
1864 | rule = ctors.addRule(selector, 'ctors');
1865 | }
1866 |
1867 | rule[name] = func;
1868 |
1869 | if (!ctors[name+'proxy']) {
1870 | var one = this.one('ctors', name);
1871 | var namespace = this;
1872 |
1873 | var proxy = function(els, i, func) {
1874 | var j = els.length;
1875 | while (j--) {
1876 | var el = els[j];
1877 |
1878 | var tmp_i = el.i, tmp_f = el.f;
1879 | el.i = i; el.f = one;
1880 |
1881 | try { func.call(namespace.$(el)); }
1882 | catch(e) { $.entwine.warn_exception(name, el, e); }
1883 | finally { el.i = tmp_i; el.f = tmp_f; }
1884 | }
1885 | };
1886 |
1887 | ctors[name+'proxy'] = proxy;
1888 | }
1889 | }
1890 | });
1891 |
1892 | $.entwine.Namespace.addHandler({
1893 | order: 30,
1894 |
1895 | bind: function(selector, k, v) {
1896 | if ($.isFunction(v) && (k == 'onmatch' || k == 'onunmatch')) {
1897 | // When we add new matchers we need to trigger a full global recalc once, regardless of the DOM changes that triggered the event
1898 | this.matchersDirty = true;
1899 |
1900 | this.bind_condesc(selector, k, v);
1901 | return true;
1902 | }
1903 | }
1904 | });
1905 |
1906 | /**
1907 | * Finds all the elements that now match a different rule (or have been removed) and call onmatch on onunmatch as appropriate
1908 | *
1909 | * Because this has to scan the DOM, and is therefore fairly slow, this is normally triggered off a short timeout, so that
1910 | * a series of DOM manipulations will only trigger this once.
1911 | *
1912 | * The downside of this is that things like:
1913 | * $('#foo').addClass('tabs'); $('#foo').tabFunctionBar();
1914 | * won't work.
1915 | */
1916 | $(document).bind('EntwineSubtreeMaybeChanged', function(e, changes){
1917 | // var start = (new Date).getTime();
1918 |
1919 | // For every namespace
1920 | for (var k in $.entwine.namespaces) {
1921 | var namespace = $.entwine.namespaces[k];
1922 |
1923 | // That has constructors or destructors
1924 | var ctors = namespace.store.ctors;
1925 | if (ctors) {
1926 |
1927 | // Keep a record of elements that have matched some previous more specific rule.
1928 | // Not that we _don't_ actually do that until this is needed. If matched is null, it's not been calculated yet.
1929 | // We also keep track of any elements that have newly been taken or released by a specific rule
1930 | var matched = null, taken = $([]), released = $([]);
1931 |
1932 | // Updates matched to contain all the previously matched elements as if we'd been keeping track all along
1933 | var calcmatched = function(j){
1934 | if (matched !== null) return;
1935 | matched = $([]);
1936 |
1937 | var cache, k = ctors.length;
1938 | while ((--k) > j) {
1939 | if (cache = ctors[k].cache) matched = matched.add(cache);
1940 | }
1941 | }
1942 |
1943 | // Some declared variables used in the loop
1944 | var add, rem, res, rule, sel, ctor, dtor, full;
1945 |
1946 | // Stepping through each selector from most to least specific
1947 | var j = ctors.length;
1948 | while (j--) {
1949 | // Build some quick-access variables
1950 | rule = ctors[j];
1951 | sel = rule.selector.selector;
1952 | ctor = rule.onmatch;
1953 | dtor = rule.onunmatch;
1954 |
1955 | /*
1956 | Rule.cache might be stale or fresh. It'll be stale if
1957 | - some more specific selector now has some of rule.cache in it
1958 | - some change has happened that means new elements match this selector now
1959 | - some change has happened that means elements no longer match this selector
1960 |
1961 | The first we can just compare rules.cache with matched, removing anything that's there already.
1962 | */
1963 |
1964 | // Reset the "elements that match this selector and no more specific selector with an onmatch rule" to null.
1965 | // Staying null means this selector is fresh.
1966 | res = null;
1967 |
1968 | // If this gets changed to true, it's too hard to do a delta update, so do a full update
1969 | full = false;
1970 |
1971 | if (namespace.matchersDirty || changes.global) {
1972 | // For now, just fall back to old version. We need to do something like changed.Subtree.find('*').andSelf().filter(sel), but that's _way_ slower on modern browsers than the below
1973 | full = true;
1974 | }
1975 | else {
1976 | // We don't deal with attributes yet, so any attribute change means we need to do a full recalc
1977 | for (var k in changes.attrs) { full = true; break; }
1978 |
1979 | /*
1980 | If a class changes, but it isn't listed in our selector, we don't care - the change couldn't affect whether or not any element matches
1981 |
1982 | If it is listed on our selector
1983 | - If it is on the direct match part, it could have added or removed the node it changed on
1984 | - If it is on the context part, it could have added or removed any node that were previously included or excluded because of a match or failure to match with the context required on that node
1985 | - NOTE: It might be on _both_
1986 | */
1987 |
1988 | var method = rule.selector.affectedBy(changes);
1989 |
1990 | if (method.classes.context) {
1991 | full = true;
1992 | }
1993 | else {
1994 | for (var k in method.classes.direct) {
1995 | calcmatched(j);
1996 | var recheck = changes.classes[k].not(matched);
1997 |
1998 | if (res === null) {
1999 | res = rule.cache ? rule.cache.not(taken).add(released.filter(sel)) : $([]);
2000 | }
2001 |
2002 | res = res.not(recheck).add(recheck.filter(sel));
2003 | }
2004 | }
2005 | }
2006 |
2007 | if (full) {
2008 | calcmatched(j);
2009 | res = $(sel).not(matched);
2010 | }
2011 | else {
2012 | if (!res) {
2013 | // We weren't stale because of any changes to the DOM that affected this selector, but more specific
2014 | // onmatches might have caused stale-ness
2015 |
2016 | // Do any of the previous released elements match this selector?
2017 | add = released.length && released.filter(sel);
2018 |
2019 | if (add && add.length) {
2020 | // Yes, so we're stale as we need to include them. Filter for any possible taken value at the same time
2021 | res = rule.cache ? rule.cache.not(taken).add(add) : add;
2022 | }
2023 | else {
2024 | // Do we think we own any of the elements now taken by more specific rules?
2025 | rem = taken.length && rule.cache && rule.cache.filter(taken);
2026 |
2027 | if (rem && rem.length) {
2028 | // Yes, so we're stale as we need to exclude them.
2029 | res = rule.cache.not(rem);
2030 | }
2031 | }
2032 | }
2033 | }
2034 |
2035 | // Res will be null if we know we are fresh (no full needed, selector not affectedBy changes)
2036 | if (res === null) {
2037 | // If we are tracking matched, add ourselves
2038 | if (matched && rule.cache) matched = matched.add(rule.cache);
2039 | }
2040 | else {
2041 | // If this selector has a list of elements it matched against last time
2042 | if (rule.cache) {
2043 | // Find the ones that are extra this time
2044 | add = res.not(rule.cache);
2045 | rem = rule.cache.not(res);
2046 | }
2047 | else {
2048 | add = res; rem = null;
2049 | }
2050 |
2051 | if ((add && add.length) || (rem && rem.length)) {
2052 | if (rem && rem.length) {
2053 | released = released.add(rem);
2054 |
2055 | if (dtor && !rule.onunmatchRunning) {
2056 | rule.onunmatchRunning = true;
2057 | ctors.onunmatchproxy(rem, j, dtor);
2058 | rule.onunmatchRunning = false;
2059 | }
2060 | }
2061 |
2062 | // Call the constructor on the newly matched ones
2063 | if (add && add.length) {
2064 | taken = taken.add(add);
2065 | released = released.not(add);
2066 |
2067 | if (ctor && !rule.onmatchRunning) {
2068 | rule.onmatchRunning = true;
2069 | ctors.onmatchproxy(add, j, ctor);
2070 | rule.onmatchRunning = false;
2071 | }
2072 | }
2073 | }
2074 |
2075 | // If we are tracking matched, add ourselves
2076 | if (matched) matched = matched.add(res);
2077 |
2078 | // And remember this list of matching elements again this selector, so next matching we can find the unmatched ones
2079 | rule.cache = res;
2080 | }
2081 | }
2082 |
2083 | namespace.matchersDirty = false;
2084 | }
2085 | }
2086 |
2087 | // console.log((new Date).getTime() - start);
2088 | });
2089 |
2090 |
2091 | })(jQuery);
2092 | ;
2093 |
2094 |
2095 | /* src/jquery.entwine.addrem.js */
2096 |
2097 | (function($) {
2098 |
2099 | $.entwine.Namespace.addMethods({
2100 | build_addrem_proxy: function(name) {
2101 | var one = this.one(name, 'func');
2102 |
2103 | return function() {
2104 | if (this.length === 0){
2105 | return;
2106 | }
2107 | else if (this.length) {
2108 | var rv, i = this.length;
2109 | while (i--) rv = one(this[i], arguments);
2110 | return rv;
2111 | }
2112 | else {
2113 | return one(this, arguments);
2114 | }
2115 | };
2116 | },
2117 |
2118 | bind_addrem_proxy: function(selector, name, func) {
2119 | var rulelist = this.store[name] || (this.store[name] = $.entwine.RuleList());
2120 |
2121 | var rule = rulelist.addRule(selector, name); rule.func = func;
2122 |
2123 | if (!this.injectee.hasOwnProperty(name)) {
2124 | this.injectee[name] = this.build_addrem_proxy(name);
2125 | this.injectee[name].isentwinemethod = true;
2126 | }
2127 | }
2128 | });
2129 |
2130 | $.entwine.Namespace.addHandler({
2131 | order: 30,
2132 |
2133 | bind: function(selector, k, v) {
2134 | if ($.isFunction(v) && (k == 'onadd' || k == 'onremove')) {
2135 | this.bind_addrem_proxy(selector, k, v);
2136 | return true;
2137 | }
2138 | }
2139 | });
2140 |
2141 | $(document).bind('EntwineElementsAdded', function(e){
2142 | // For every namespace
2143 | for (var k in $.entwine.namespaces) {
2144 | var namespace = $.entwine.namespaces[k];
2145 | if (namespace.injectee.onadd) namespace.injectee.onadd.call(e.targets);
2146 | }
2147 | });
2148 |
2149 | $(document).bind('EntwineElementsRemoved', function(e){
2150 | for (var k in $.entwine.namespaces) {
2151 | var namespace = $.entwine.namespaces[k];
2152 | if (namespace.injectee.onremove) namespace.injectee.onremove.call(e.targets);
2153 | }
2154 | });
2155 |
2156 |
2157 |
2158 |
2159 | })(jQuery);
2160 | ;
2161 |
2162 |
2163 | /* src/jquery.entwine.properties.js */
2164 |
2165 | (function($) {
2166 |
2167 | var entwine_prepend = '__entwine!';
2168 |
2169 | var getEntwineData = function(el, namespace, property) {
2170 | return el.data(entwine_prepend + namespace + '!' + property);
2171 | };
2172 |
2173 | var setEntwineData = function(el, namespace, property, value) {
2174 | return el.data(entwine_prepend + namespace + '!' + property, value);
2175 | };
2176 |
2177 | var getEntwineDataAsHash = function(el, namespace) {
2178 | var hash = {};
2179 | var id = jQuery.data(el[0]);
2180 |
2181 | var matchstr = entwine_prepend + namespace + '!';
2182 | var matchlen = matchstr.length;
2183 |
2184 | var cache = jQuery.cache[id];
2185 | for (var k in cache) {
2186 | if (k.substr(0,matchlen) == matchstr) hash[k.substr(matchlen)] = cache[k];
2187 | }
2188 |
2189 | return hash;
2190 | };
2191 |
2192 | var setEntwineDataFromHash = function(el, namespace, hash) {
2193 | for (var k in hash) setEntwineData(namespace, k, hash[k]);
2194 | };
2195 |
2196 | var entwineData = function(el, namespace, args) {
2197 | switch (args.length) {
2198 | case 0:
2199 | return getEntwineDataAsHash(el, namespace);
2200 | case 1:
2201 | if (typeof args[0] == 'string') return getEntwineData(el, namespace, args[0]);
2202 | else return setEntwineDataFromHash(el, namespace, args[0]);
2203 | default:
2204 | return setEntwineData(el, namespace, args[0], args[1]);
2205 | }
2206 | };
2207 |
2208 | $.extend($.fn, {
2209 | entwineData: function() {
2210 | return entwineData(this, '__base', arguments);
2211 | }
2212 | });
2213 |
2214 | $.entwine.Namespace.addHandler({
2215 | order: 60,
2216 |
2217 | bind: function(selector, k, v) {
2218 | if (k.charAt(0) != k.charAt(0).toUpperCase()) $.entwine.warn('Entwine property '+k+' does not start with a capital letter', $.entwine.WARN_LEVEL_BESTPRACTISE);
2219 |
2220 | // Create the getters and setters
2221 |
2222 | var getterName = 'get'+k;
2223 | var setterName = 'set'+k;
2224 |
2225 | this.bind_proxy(selector, getterName, function() { var r = this.entwineData(k); return r === undefined ? v : r; });
2226 | this.bind_proxy(selector, setterName, function(v){ return this.entwineData(k, v); });
2227 |
2228 | // Get the get and set proxies we just created
2229 |
2230 | var getter = this.injectee[getterName];
2231 | var setter = this.injectee[setterName];
2232 |
2233 | // And bind in the jQuery-style accessor
2234 |
2235 | this.bind_proxy(selector, k, function(v){ return (arguments.length == 1 ? setter : getter).call(this, v) ; });
2236 |
2237 | return true;
2238 | },
2239 |
2240 | namespaceMethodOverrides: function(namespace){
2241 | return {
2242 | entwineData: function() {
2243 | return entwineData(this, namespace.name, arguments);
2244 | }
2245 | };
2246 | }
2247 | });
2248 |
2249 | })(jQuery);
2250 | ;
2251 |
2252 |
2253 | /* src/jquery.entwine.legacy.js */
2254 |
2255 | (function($) {
2256 |
2257 | // Adds back concrete methods for backwards compatibility
2258 | $.concrete = $.entwine;
2259 | $.fn.concrete = $.fn.entwine;
2260 | $.fn.concreteData = $.fn.entwineData;
2261 |
2262 | // Use addHandler to hack in the namespace.$.concrete equivilent to the namespace.$.entwine namespace-injection
2263 | $.entwine.Namespace.addHandler({
2264 | order: 100,
2265 | bind: function(selector, k, v) { return false; },
2266 |
2267 | namespaceMethodOverrides: function(namespace){
2268 | namespace.$.concrete = namespace.$.entwine;
2269 | namespace.injectee.concrete = namespace.injectee.entwine;
2270 | namespace.injectee.concreteData = namespace.injectee.entwineData;
2271 | return {};
2272 | }
2273 | });
2274 |
2275 | })(jQuery);
2276 | ;
2277 |
2278 |
--------------------------------------------------------------------------------
/javascript/externals/silverstripe/lib.js:
--------------------------------------------------------------------------------
1 | (function($) {
2 |
3 | // Copyright (c) 2011 John Resig, http://jquery.com/
4 |
5 | // Permission is hereby granted, free of charge, to any person obtaining
6 | // a copy of this software and associated documentation files (the
7 | // "Software"), to deal in the Software without restriction, including
8 | // without limitation the rights to use, copy, modify, merge, publish,
9 | // distribute, sublicense, and/or sell copies of the Software, and to
10 | // permit persons to whom the Software is furnished to do so, subject to
11 | // the following conditions:
12 |
13 | // The above copyright notice and this permission notice shall be
14 | // included in all copies or substantial portions of the Software.
15 |
16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | //define vars for interal use
25 | var $window = $( window ),
26 | $html = $( 'html' ),
27 | $head = $( 'head' ),
28 |
29 | //url path helpers for use in relative url management
30 | path = {
31 |
32 | // This scary looking regular expression parses an absolute URL or its relative
33 | // variants (protocol, site, document, query, and hash), into the various
34 | // components (protocol, host, path, query, fragment, etc that make up the
35 | // URL as well as some other commonly used sub-parts. When used with RegExp.exec()
36 | // or String.match, it parses the URL into a results array that looks like this:
37 | //
38 | // [0]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread#msg-content
39 | // [1]: http://jblas:password@mycompany.com:8080/mail/inbox?msg=1234&type=unread
40 | // [2]: http://jblas:password@mycompany.com:8080/mail/inbox
41 | // [3]: http://jblas:password@mycompany.com:8080
42 | // [4]: http:
43 | // [5]: //
44 | // [6]: jblas:password@mycompany.com:8080
45 | // [7]: jblas:password
46 | // [8]: jblas
47 | // [9]: password
48 | // [10]: mycompany.com:8080
49 | // [11]: mycompany.com
50 | // [12]: 8080
51 | // [13]: /mail/inbox
52 | // [14]: /mail/
53 | // [15]: inbox
54 | // [16]: ?msg=1234&type=unread
55 | // [17]: #msg-content
56 | //
57 | urlParseRE: /^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,
58 |
59 | //Parse a URL into a structure that allows easy access to
60 | //all of the URL components by name.
61 | parseUrl: function( url ) {
62 | // If we're passed an object, we'll assume that it is
63 | // a parsed url object and just return it back to the caller.
64 | if ( $.type( url ) === "object" ) {
65 | return url;
66 | }
67 |
68 | var matches = path.urlParseRE.exec( url || "" ) || [];
69 |
70 | // Create an object that allows the caller to access the sub-matches
71 | // by name. Note that IE returns an empty string instead of undefined,
72 | // like all other browsers do, so we normalize everything so its consistent
73 | // no matter what browser we're running on.
74 | return {
75 | href: matches[ 0 ] || "",
76 | hrefNoHash: matches[ 1 ] || "",
77 | hrefNoSearch: matches[ 2 ] || "",
78 | domain: matches[ 3 ] || "",
79 | protocol: matches[ 4 ] || "",
80 | doubleSlash: matches[ 5 ] || "",
81 | authority: matches[ 6 ] || "",
82 | username: matches[ 8 ] || "",
83 | password: matches[ 9 ] || "",
84 | host: matches[ 10 ] || "",
85 | hostname: matches[ 11 ] || "",
86 | port: matches[ 12 ] || "",
87 | pathname: matches[ 13 ] || "",
88 | directory: matches[ 14 ] || "",
89 | filename: matches[ 15 ] || "",
90 | search: matches[ 16 ] || "",
91 | hash: matches[ 17 ] || ""
92 | };
93 | },
94 |
95 | //Turn relPath into an asbolute path. absPath is
96 | //an optional absolute path which describes what
97 | //relPath is relative to.
98 | makePathAbsolute: function( relPath, absPath ) {
99 | if ( relPath && relPath.charAt( 0 ) === "/" ) {
100 | return relPath;
101 | }
102 |
103 | relPath = relPath || "";
104 | absPath = absPath ? absPath.replace( /^\/|(\/[^\/]*|[^\/]+)$/g, "" ) : "";
105 |
106 | var absStack = absPath ? absPath.split( "/" ) : [],
107 | relStack = relPath.split( "/" );
108 | for ( var i = 0; i < relStack.length; i++ ) {
109 | var d = relStack[ i ];
110 | switch ( d ) {
111 | case ".":
112 | break;
113 | case "..":
114 | if ( absStack.length ) {
115 | absStack.pop();
116 | }
117 | break;
118 | default:
119 | absStack.push( d );
120 | break;
121 | }
122 | }
123 | return "/" + absStack.join( "/" );
124 | },
125 |
126 | //Returns true if both urls have the same domain.
127 | isSameDomain: function( absUrl1, absUrl2 ) {
128 | return path.parseUrl( absUrl1 ).domain === path.parseUrl( absUrl2 ).domain;
129 | },
130 |
131 | //Returns true for any relative variant.
132 | isRelativeUrl: function( url ) {
133 | // All relative Url variants have one thing in common, no protocol.
134 | return path.parseUrl( url ).protocol === "";
135 | },
136 |
137 | //Returns true for an absolute url.
138 | isAbsoluteUrl: function( url ) {
139 | return path.parseUrl( url ).protocol !== "";
140 | },
141 |
142 | //Turn the specified realtive URL into an absolute one. This function
143 | //can handle all relative variants (protocol, site, document, query, fragment).
144 | makeUrlAbsolute: function( relUrl, absUrl ) {
145 | if ( !path.isRelativeUrl( relUrl ) ) {
146 | return relUrl;
147 | }
148 |
149 | var relObj = path.parseUrl( relUrl ),
150 | absObj = path.parseUrl( absUrl ),
151 | protocol = relObj.protocol || absObj.protocol,
152 | doubleSlash = relObj.protocol ? relObj.doubleSlash : ( relObj.doubleSlash || absObj.doubleSlash ),
153 | authority = relObj.authority || absObj.authority,
154 | hasPath = relObj.pathname !== "",
155 | pathname = path.makePathAbsolute( relObj.pathname || absObj.filename, absObj.pathname ),
156 | search = relObj.search || ( !hasPath && absObj.search ) || "",
157 | hash = relObj.hash;
158 |
159 | return protocol + doubleSlash + authority + pathname + search + hash;
160 | },
161 |
162 | //Add search (aka query) params to the specified url.
163 | // 2013-12-06 ischommer: Customized to merge with existing keys
164 | addSearchParams: function( url, params ) {
165 | var u = path.parseUrl( url ),
166 | params = ( typeof params === "string" ) ? path.convertSearchToArray( params ) : params,
167 | newParams = $.extend( path.convertSearchToArray( u.search ), params );
168 | return u.hrefNoSearch + '?' + $.param( newParams ) + ( u.hash || "" );
169 | },
170 |
171 | // 2013-12-06 ischommer: Added to allow merge with existing keys
172 | getSearchParams: function(url) {
173 | var u = path.parseUrl( url );
174 | return path.convertSearchToArray( u.search );
175 | },
176 |
177 | // Converts query strings (foo=bar&baz=bla) to a hash.
178 | // TODO Handle repeating elements (e.g. arr[]=one&arr[]=two)
179 | // 2013-12-06 ischommer: Added to allow merge with existing keys
180 | convertSearchToArray: function(search) {
181 | var params = {},
182 | search = search.replace( /^\?/, '' ),
183 | parts = search ? search.split( '&' ) : [], i, tmp;
184 | for(i=0; i < parts.length; i++) {
185 | tmp = parts[i].split( '=' );
186 | params[tmp[0]] = tmp[1];
187 | }
188 | return params;
189 | },
190 |
191 | convertUrlToDataUrl: function( absUrl ) {
192 | var u = path.parseUrl( absUrl );
193 | if ( path.isEmbeddedPage( u ) ) {
194 | // For embedded pages, remove the dialog hash key as in getFilePath(),
195 | // otherwise the Data Url won't match the id of the embedded Page.
196 | return u.hash.split( dialogHashKey )[0].replace( /^#/, "" );
197 | } else if ( path.isSameDomain( u, document ) ) {
198 | return u.hrefNoHash.replace( document.domain, "" );
199 | }
200 | return absUrl;
201 | },
202 |
203 | //get path from current hash, or from a file path
204 | get: function( newPath ) {
205 | if( newPath === undefined ) {
206 | newPath = location.hash;
207 | }
208 | return path.stripHash( newPath ).replace( /[^\/]*\.[^\/*]+$/, '' );
209 | },
210 |
211 | //return the substring of a filepath before the sub-page key, for making a server request
212 | getFilePath: function( path ) {
213 | var splitkey = '&' + $.mobile.subPageUrlKey;
214 | return path && path.split( splitkey )[0].split( dialogHashKey )[0];
215 | },
216 |
217 | //set location hash to path
218 | set: function( path ) {
219 | location.hash = path;
220 | },
221 |
222 | //test if a given url (string) is a path
223 | //NOTE might be exceptionally naive
224 | isPath: function( url ) {
225 | return ( /\// ).test( url );
226 | },
227 |
228 | //return a url path with the window's location protocol/hostname/pathname removed
229 | clean: function( url ) {
230 | return url.replace( document.domain, "" );
231 | },
232 |
233 | //just return the url without an initial #
234 | stripHash: function( url ) {
235 | return url.replace( /^#/, "" );
236 | },
237 |
238 | //remove the preceding hash, any query params, and dialog notations
239 | cleanHash: function( hash ) {
240 | return path.stripHash( hash.replace( /\?.*$/, "" ).replace( dialogHashKey, "" ) );
241 | },
242 |
243 | //check whether a url is referencing the same domain, or an external domain or different protocol
244 | //could be mailto, etc
245 | isExternal: function( url ) {
246 | var u = path.parseUrl( url );
247 | return u.protocol && u.domain !== document.domain ? true : false;
248 | },
249 |
250 | hasProtocol: function( url ) {
251 | return ( /^(:?\w+:)/ ).test( url );
252 | }
253 | };
254 |
255 | $.path = path;
256 | }(jQuery));
257 |
--------------------------------------------------------------------------------
/javascript/externals/silverstripe/ssui.core.js:
--------------------------------------------------------------------------------
1 | (function($) {
2 |
3 | /**
4 | * Allows icon definition via HTML5 data attrs for easier handling in PHP.
5 | *
6 | * Adds an alternative appearance so we can toggle back and forth between them
7 | * and register event handlers to add custom styling and behaviour. Example use
8 | * is in the CMS with the saving buttons - depending on the page's state one of
9 | * them will either say "Save draft" or "Saved", and will have different colour.
10 | */
11 | $.widget('ssui.button', $.ui.button, {
12 | options: {
13 | alternate: {
14 | icon: null,
15 | text: null
16 | },
17 | showingAlternate: false
18 | },
19 |
20 | /**
21 | * Switch between the alternate appearances.
22 | */
23 | toggleAlternate: function() {
24 | if (this._trigger('ontogglealternate')===false) return;
25 |
26 | // Only switch to alternate if it has been enabled through options.
27 | if (!this.options.alternate.icon && !this.options.alternate.text) return;
28 |
29 | this.options.showingAlternate = !this.options.showingAlternate;
30 | this.refresh();
31 | },
32 |
33 | /**
34 | * Adjust the appearance to fit with the current settings.
35 | */
36 | _refreshAlternate: function() {
37 | this._trigger('beforerefreshalternate');
38 |
39 | // Only switch to alternate if it has been enabled through options.
40 | if (!this.options.alternate.icon && !this.options.alternate.text) return;
41 |
42 | if (this.options.showingAlternate) {
43 | this.element.find('.ui-button-icon-primary').hide();
44 | this.element.find('.ui-button-text').hide();
45 | this.element.find('.ui-button-icon-alternate').show();
46 | this.element.find('.ui-button-text-alternate').show();
47 | }
48 | else {
49 | this.element.find('.ui-button-icon-primary').show();
50 | this.element.find('.ui-button-text').show();
51 | this.element.find('.ui-button-icon-alternate').hide();
52 | this.element.find('.ui-button-text-alternate').hide();
53 | }
54 |
55 | this._trigger('afterrefreshalternate');
56 | },
57 |
58 | /**
59 | * Construct button - pulls in options from data attributes.
60 | * Injects new elements for alternate appearance (if requested via options).
61 | */
62 | _resetButton: function() {
63 | var iconPrimary = this.element.data('icon-primary'),
64 | iconSecondary = this.element.data('icon-secondary');
65 |
66 | if (!iconPrimary) iconPrimary = this.element.data('icon');
67 |
68 | // TODO Move prefix out of this method, without requriing it for every icon definition in a data attr
69 | if(iconPrimary) this.options.icons.primary = 'btn-icon-' + iconPrimary;
70 | if(iconSecondary) this.options.icons.secondary = 'btn-icon-' + iconSecondary;
71 |
72 | $.ui.button.prototype._resetButton.call(this);
73 |
74 | // Pull options from data attributes. Overriden by explicit options given on widget creation.
75 | if (!this.options.alternate.text) {
76 | this.options.alternate.text = this.element.data('text-alternate');
77 | }
78 | if (!this.options.alternate.icon) {
79 | this.options.alternate.icon = this.element.data('icon-alternate');
80 | }
81 | if (!this.options.showingAlternate) {
82 | this.options.showingAlternate = this.element.hasClass('ss-ui-alternate');
83 | }
84 |
85 | // Create missing elements.
86 | if (this.options.alternate.icon) {
87 | this.buttonElement.append(
88 | ""
90 | );
91 | }
92 | if (this.options.alternate.text) {
93 | this.buttonElement.append(
94 | "" + this.options.alternate.text + ""
95 | );
96 | }
97 |
98 | this._refreshAlternate();
99 | },
100 |
101 | refresh: function() {
102 | $.ui.button.prototype.refresh.call(this);
103 |
104 | this._refreshAlternate();
105 | },
106 |
107 | destroy: function() {
108 | this.element.find('.ui-button-text-alternate').remove();
109 | this.element.find('.ui-button-icon-alternate').remove();
110 |
111 | $.ui.button.prototype.destroy.call( this );
112 | }
113 | });
114 |
115 | /**
116 | * Extends jQueryUI dialog with iframe abilities (and related resizing logic),
117 | * and sets some CMS-wide defaults.
118 | *
119 | * Additional settings:
120 | * - 'autoPosition': Automatically reposition window on resize based on 'position' option
121 | * - 'widthRatio': Sets width based on percentage of window (value between 0 and 1)
122 | * - 'heightRatio': Sets width based on percentage of window (value between 0 and 1)
123 | * - 'reloadOnOpen': Reloads the iframe whenever the dialog is reopened
124 | * - 'iframeUrl': Create an iframe element and load this URL when the dialog is created
125 | */
126 | $.widget("ssui.ssdialog", $.ui.dialog, {
127 | options: {
128 | // Custom properties
129 | iframeUrl: '',
130 | reloadOnOpen: true,
131 | dialogExtraClass: '',
132 |
133 | // Defaults
134 | modal: true,
135 | bgiframe: true,
136 | autoOpen: false,
137 | autoPosition: true,
138 | minWidth: 500,
139 | maxWidth: 700,
140 | minHeight: 300,
141 | maxHeight: 600,
142 | widthRatio: 0.8,
143 | heightRatio: 0.8,
144 | resizable: false
145 | },
146 | _create: function() {
147 | $.ui.dialog.prototype._create.call(this);
148 |
149 | var self = this;
150 |
151 | // Create iframe
152 | var iframe = $('');
153 | iframe.bind('load', function(e) {
154 | if($(this).attr('src') == 'about:blank') return;
155 |
156 | iframe.addClass('loaded').show(); // more reliable than 'src' attr check (in IE)
157 | self._resizeIframe();
158 | self.uiDialog.removeClass('loading');
159 | }).hide();
160 |
161 | if(this.options.dialogExtraClass) this.uiDialog.addClass(this.options.dialogExtraClass);
162 | this.element.append(iframe);
163 |
164 | // Let the iframe handle its scrolling
165 | if(this.options.iframeUrl) this.element.css('overflow', 'hidden');
166 | },
167 | open: function() {
168 | $.ui.dialog.prototype.open.call(this);
169 |
170 | var self = this, iframe = this.element.children('iframe');
171 |
172 | // Load iframe
173 | if(this.options.iframeUrl && (!iframe.hasClass('loaded') || this.options.reloadOnOpen)) {
174 | iframe.hide();
175 | iframe.attr('src', this.options.iframeUrl);
176 | this.uiDialog.addClass('loading');
177 | }
178 |
179 | // Resize events
180 | $(window).bind('resize.ssdialog', function() {self._resizeIframe();});
181 | },
182 | close: function() {
183 | $.ui.dialog.prototype.close.call(this);
184 |
185 | this.uiDialog.unbind('resize.ssdialog');
186 | $(window).unbind('resize.ssdialog');
187 | },
188 | _resizeIframe: function() {
189 | var opts = {}, newWidth, newHeight, iframe = this.element.children('iframe');;
190 | if(this.options.widthRatio) {
191 | newWidth = $(window).width() * this.options.widthRatio;
192 | if(this.options.minWidth && newWidth < this.options.minWidth) {
193 | opts.width = this.options.minWidth
194 | } else if(this.options.maxWidth && newWidth > this.options.maxWidth) {
195 | opts.width = this.options.maxWidth;
196 | } else {
197 | opts.width = newWidth;
198 | }
199 | }
200 | if(this.options.heightRatio) {
201 | newHeight = $(window).height() * this.options.heightRatio;
202 | if(this.options.minHeight && newHeight < this.options.minHeight) {
203 | opts.height = this.options.minHeight
204 | } else if(this.options.maxHeight && newHeight > this.options.maxHeight) {
205 | opts.height = this.options.maxHeight;
206 | } else {
207 | opts.height = newHeight;
208 | }
209 | }
210 |
211 | if(!jQuery.isEmptyObject(opts)) {
212 | this._setOptions(opts);
213 |
214 | // Resize iframe within dialog
215 | iframe.attr('width',
216 | opts.width
217 | - parseFloat(this.element.css('paddingLeft'))
218 | - parseFloat(this.element.css('paddingRight'))
219 | );
220 | iframe.attr('height',
221 | opts.height
222 | - parseFloat(this.element.css('paddingTop'))
223 | - parseFloat(this.element.css('paddingBottom'))
224 | );
225 |
226 | // Enforce new position
227 | if(this.options.autoPosition) {
228 | this._setOption("position", this.options.position);
229 | }
230 | }
231 | }
232 | });
233 |
234 | $.widget("ssui.titlebar", {
235 | _create: function() {
236 | this.originalTitle = this.element.attr('title');
237 |
238 | var self = this;
239 | var options = this.options;
240 |
241 | var title = options.title || this.originalTitle || ' ';
242 | var titleId = $.ui.dialog.getTitleId(this.element);
243 |
244 | this.element.parent().addClass('ui-dialog');
245 |
246 | var uiDialogTitlebar = this.element.
247 | addClass(
248 | 'ui-dialog-titlebar ' +
249 | 'ui-widget-header ' +
250 | 'ui-corner-all ' +
251 | 'ui-helper-clearfix'
252 | );
253 |
254 | // By default, the
255 |
256 | if(options.closeButton) {
257 | var uiDialogTitlebarClose = $('')
258 | .addClass(
259 | 'ui-dialog-titlebar-close ' +
260 | 'ui-corner-all'
261 | )
262 | .attr('role', 'button')
263 | .hover(
264 | function() {
265 | uiDialogTitlebarClose.addClass('ui-state-hover');
266 | },
267 | function() {
268 | uiDialogTitlebarClose.removeClass('ui-state-hover');
269 | }
270 | )
271 | .focus(function() {
272 | uiDialogTitlebarClose.addClass('ui-state-focus');
273 | })
274 | .blur(function() {
275 | uiDialogTitlebarClose.removeClass('ui-state-focus');
276 | })
277 | .mousedown(function(ev) {
278 | ev.stopPropagation();
279 | })
280 | .appendTo(uiDialogTitlebar);
281 |
282 | var uiDialogTitlebarCloseText = (this.uiDialogTitlebarCloseText = $(''))
283 | .addClass(
284 | 'ui-icon ' +
285 | 'ui-icon-closethick'
286 | )
287 | .text(options.closeText)
288 | .appendTo(uiDialogTitlebarClose);
289 | }
290 |
291 | var uiDialogTitle = $('')
292 | .addClass('ui-dialog-title')
293 | .attr('id', titleId)
294 | .html(title)
295 | .prependTo(uiDialogTitlebar);
296 |
297 | uiDialogTitlebar.find("*").add(uiDialogTitlebar).disableSelection();
298 | },
299 |
300 | destroy: function() {
301 | this.element
302 | .unbind('.dialog')
303 | .removeData('dialog')
304 | .removeClass('ui-dialog-content ui-widget-content')
305 | .hide().appendTo('body');
306 |
307 | (this.originalTitle && this.element.attr('title', this.originalTitle));
308 | }
309 | });
310 |
311 | $.extend($.ssui.titlebar, {
312 | version: "0.0.1",
313 | options: {
314 | title: '',
315 | closeButton: false,
316 | closeText: 'close'
317 | },
318 |
319 | uuid: 0,
320 |
321 | getTitleId: function($el) {
322 | return 'ui-dialog-title-' + ($el.attr('id') || ++this.uuid);
323 | }
324 | });
325 | }(jQuery));
326 |
--------------------------------------------------------------------------------
/src/Forms/GridField/GridField.php:
--------------------------------------------------------------------------------
1 | getUseAdminAPI()) {
33 | Requirements::javascriptTemplate(dirname(__FILE__) . '/../../../javascript/boot.template.js', [
34 | 'SecurityID' => Convert::raw2js(SecurityToken::inst()->getValue()),
35 | 'AbsoluteBaseURL' => Convert::raw2js(Director::absoluteBaseURL()),
36 | 'BaseURL' => Convert::raw2js(Director::baseURL()),
37 | 'Environment' => Convert::raw2js(Director::get_environment_type()),
38 | 'Debugging' => (Director::isDev() ? 'true' : 'false')
39 | ]);
40 |
41 | Requirements::javascript('silverstripe/admin: client/dist/js/vendor.js');
42 | Requirements::javascript('silverstripe/admin: client/dist/js/bundle.js');
43 | Requirements::add_i18n_javascript('silverstripe/admin: javascript/lang');
44 | } else {
45 | Requirements::javascript('https://code.jquery.com/jquery-3.7.0.min.js');
46 | Requirements::javascript('https://code.jquery.com/jquery-migrate-1.4.1.min.js');
47 | Requirements::javascript('silverstripe/admin: thirdparty/jquery-ui/jquery-ui.js');
48 | Requirements::javascript('webbuilders-group/silverstripe-frontendgridfield: javascript/externals/hafriedlander/jquery-entwine/jquery.entwine-dist.js');
49 | Requirements::javascript('silverstripe/admin: client/dist/js/i18n.js');
50 | Requirements::add_i18n_javascript('silverstripe/admin: javascript/lang');
51 | Requirements::javascript('webbuilders-group/silverstripe-frontendgridfield: javascript/externals/silverstripe/lib.js');
52 | Requirements::javascript('webbuilders-group/silverstripe-frontendgridfield: javascript/externals/silverstripe/ssui.core.js');
53 | Requirements::javascript('webbuilders-group/silverstripe-frontendgridfield: javascript/GridField.js');
54 | }
55 |
56 | Requirements::javascript('webbuilders-group/silverstripe-frontendgridfield: javascript/FrontEndGridField.js');
57 |
58 |
59 | return parent::FieldHolder();
60 | }
61 |
62 | /**
63 | * Allow this GridField to use the Admin's API, you should not mix and match this otherwise you will run into issues!
64 | * @param bool $value Whether or not to use the admin's api
65 | * @return GridField
66 | */
67 | public function setUseAdminAPI($value)
68 | {
69 | $this->useAdminAPI = $value;
70 | return $this;
71 | }
72 |
73 | /**
74 | * Get's whether this GridField is to use the Admin's API
75 | * @return Whether or not to use the admin's api
76 | */
77 | public function getUseAdminAPI()
78 | {
79 | return $this->useAdminAPI || $this->config()->use_admin_api;
80 | }
81 |
82 | /**
83 | * Gets the type for this field
84 | * @return string
85 | */
86 | public function Type()
87 | {
88 | return 'frontendgrid ' . parent::Type();
89 | }
90 |
91 | /**
92 | * Custom Readonly transformation to remove actions which shouldn't be present for a readonly state.
93 | * @return GridField
94 | */
95 | public function performReadonlyTransformation()
96 | {
97 | $this->readonlyComponents[] = GridFieldDetailForm::class;
98 |
99 | return parent::performReadonlyTransformation();
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/Forms/GridField/GridFieldConfig_Base.php:
--------------------------------------------------------------------------------
1 | use_admin_api) {
18 | $this->removeComponentsByType(GridField_ActionMenu::class);
19 | }
20 |
21 | //Use the legacy filter header as the GraphQL/React one will not work
22 | $filterHeader = $this->getComponentByType(GridFieldFilterHeader::class);
23 | if ($filterHeader) {
24 | if (property_exists($filterHeader, 'useLegacyFilterHeader')) {
25 | $filterHeader->useLegacyFilterHeader = true;
26 | } else {
27 | $this->removeComponent($filterHeader);
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Forms/GridField/GridFieldConfig_RecordEditor.php:
--------------------------------------------------------------------------------
1 | removeComponentsByType(SS_GridFieldDetailForm::class)
20 | ->addComponent(new GridFieldDetailForm());
21 |
22 | if (!GridField::config()->use_admin_api) {
23 | $this->removeComponentsByType(GridField_ActionMenu::class);
24 | }
25 |
26 | //Use the legacy filter header as the GraphQL/React one will not work
27 | $filterHeader = $this->getComponentByType(GridFieldFilterHeader::class);
28 | if ($filterHeader) {
29 | if (property_exists($filterHeader, 'useLegacyFilterHeader')) {
30 | $filterHeader->useLegacyFilterHeader = true;
31 | } else {
32 | $this->removeComponent($filterHeader);
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Forms/GridField/GridFieldConfig_RecordViewer.php:
--------------------------------------------------------------------------------
1 | removeComponentsByType(SS_GridFieldDetailForm::class)
20 | ->addComponent(new GridFieldDetailForm());
21 |
22 | if (!GridField::config()->use_admin_api) {
23 | $this->removeComponentsByType(GridField_ActionMenu::class);
24 | }
25 |
26 | //Use the legacy filter header as the GraphQL/React one will not work
27 | $filterHeader = $this->getComponentByType(GridFieldFilterHeader::class);
28 | if ($filterHeader) {
29 | if (property_exists($filterHeader, 'useLegacyFilterHeader')) {
30 | $filterHeader->useLegacyFilterHeader = true;
31 | } else {
32 | $this->removeComponent($filterHeader);
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Forms/GridField/GridFieldConfig_RelationEditor.php:
--------------------------------------------------------------------------------
1 | removeComponentsByType(SS_GridFieldDetailForm::class)
20 | ->addComponent(new GridFieldDetailForm());
21 |
22 | if (!GridField::config()->use_admin_api) {
23 | $this->removeComponentsByType(GridField_ActionMenu::class);
24 | }
25 |
26 | //Use the legacy filter header as the GraphQL/React one will not work
27 | $filterHeader = $this->getComponentByType(GridFieldFilterHeader::class);
28 | if ($filterHeader) {
29 | if (property_exists($filterHeader, 'useLegacyFilterHeader')) {
30 | $filterHeader->useLegacyFilterHeader = true;
31 | } else {
32 | $this->removeComponent($filterHeader);
33 | }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Forms/GridField/GridFieldDetailForm.php:
--------------------------------------------------------------------------------
1 | gridField->getList();
34 |
35 | if (empty($this->record)) {
36 | $controller = $this->getToplevelController();
37 | $noActionURL = $controller->removeAction($controller->getRequest()->getURL(true));
38 |
39 | $controller->getResponse()->removeHeader('Location'); //clear the existing redirect
40 |
41 | return $controller->redirect($noActionURL, 302);
42 | }
43 |
44 | $canView = $this->record->canView();
45 | $canEdit = $this->record->canEdit();
46 | $canDelete = $this->record->canDelete();
47 | $canCreate = $this->record->canCreate();
48 |
49 | if (!$canView) {
50 | return $this->getToplevelController()->httpError(403);
51 | }
52 |
53 | $actions = new FieldList();
54 | if ($this->record->ID !== 0) {
55 | if ($canEdit) {
56 | $actions->push(
57 | FormAction::create('doSave', _t('GridFieldDetailForm.Save', 'Save'))
58 | ->setUseButtonTag(true)
59 | ->addExtraClass('btn-primary font-icon-save')
60 | ->setAttribute('data-icon', 'accept')
61 | );
62 | }
63 |
64 | if ($canDelete) {
65 | $actions->push(
66 | FormAction::create('doDelete', _t('GridFieldDetailForm.Delete', 'Delete'))
67 | ->setUseButtonTag(true)
68 | ->addExtraClass('btn-outline-danger btn-hide-outline font-icon-trash-bin action-delete')
69 | );
70 | }
71 | } else { // adding new record
72 | //Change the Save label to 'Create'
73 | $actions->push(
74 | FormAction::create('doSave', _t('GridFieldDetailForm.Create', 'Create'))
75 | ->setUseButtonTag(true)
76 | ->addExtraClass('btn-primary font-icon-plus-thin')
77 | ->setAttribute('data-icon', 'add')
78 | );
79 |
80 | // Add a Cancel link which is a button-like link and link back to one level up.
81 | $curmbs = $this->Breadcrumbs();
82 | if ($curmbs && $curmbs->count() >= 2) {
83 | $one_level_up = $curmbs->offsetGet($curmbs->count() - 2);
84 | $text = sprintf(
85 | "%s",
86 | "crumb ss-ui-button btn-outline-danger btn-hide-outline font-icon-trash-bin cms-panel-link ui-corner-all", // CSS classes
87 | $one_level_up->Link, // url
88 | _t('GridFieldDetailForm.CancelBtn', 'Cancel') // label
89 | );
90 |
91 | $actions->push(new LiteralField('cancelbutton', $text));
92 | }
93 | }
94 |
95 | // If we are creating a new record in a has-many list, then
96 | // pre-populate the record's foreign key.
97 | if ($list instanceof HasManyList && !$this->record->isInDB()) {
98 | $key = $list->getForeignKey();
99 | $id = $list->getForeignID();
100 | $this->record->$key = $id;
101 | }
102 |
103 | $fields = $this->component->getFields();
104 | if (!$fields) {
105 | $fields = ($this->record->hasMethod('getFrontEndFields') ? $this->record->getFrontEndFields() : $this->record->getCMSFields());
106 | }
107 |
108 | // If we are creating a new record in a has-many list, then
109 | // Disable the form field as it has no effect.
110 | if ($list instanceof HasManyList) {
111 | $key = $list->getForeignKey();
112 |
113 | if ($field = $fields->dataFieldByName($key)) {
114 | $fields->makeFieldReadonly($field);
115 | }
116 | }
117 |
118 | // this pushes the current page ID in as a hidden field
119 | // this means the request will have the current page ID in it
120 | // rather than relying on session which can have been rewritten
121 | // by the user having another tab open
122 | // see LeftAndMain::currentPageID
123 | if ($this->controller->hasMethod('currentPageID') && $this->controller->currentPageID()) {
124 | $fields->push(new HiddenField('CMSMainCurrentPageID', null, $this->controller->currentPageID()));
125 | }
126 |
127 | // Caution: API violation. Form expects a Controller, but we are giving it a RequestHandler instead.
128 | // Thanks to this however, we are able to nest GridFields, and also access the initial Controller by
129 | // dereferencing GridFieldDetailForm_ItemRequest->getController() multiple times. See getToplevelController
130 | // below.
131 | $form = new Form(
132 | $this,
133 | 'ItemEditForm',
134 | $fields,
135 | $actions,
136 | $this->component->getValidator()
137 | );
138 |
139 | $form->loadDataFrom($this->record, ($this->record->ID == 0 ? Form::MERGE_IGNORE_FALSEISH : Form::MERGE_DEFAULT));
140 |
141 | if ($this->record->ID && !$canEdit) {
142 | // Restrict editing of existing records
143 | $form->makeReadonly();
144 |
145 | // Hack to re-enable delete button if user can delete
146 | if ($canDelete) {
147 | $form->Actions()->fieldByName('action_doDelete')->setReadonly(false);
148 | }
149 | } else if (!$this->record->ID && !$canCreate) {
150 | // Restrict creation of new records
151 | $form->makeReadonly();
152 | }
153 |
154 | // Load many_many extraData for record.
155 | // Fields with the correct 'ManyMany' namespace need to be added manually through getCMSFields().
156 | if ($list instanceof ManyManyList) {
157 | $extraData = $list->getExtraData('', $this->record->ID);
158 | $form->loadDataFrom(['ManyMany' => $extraData]);
159 | }
160 |
161 | $cb = $this->component->getItemEditFormCallback();
162 | if ($cb) {
163 | $cb($form, $this);
164 | }
165 |
166 | $this->extend("updateItemEditForm", $form);
167 | return $form;
168 | }
169 |
170 | /**
171 | * Renders the view form
172 | * @param {SS_HTTPRequest} $request Request data
173 | * @return {string} Rendered view form
174 | */
175 | public function view($request)
176 | {
177 | if (!$this->record->canView()) {
178 | $this->httpError(403);
179 | }
180 |
181 | $controller = $this->getToplevelController();
182 | $form = $this->ItemEditForm($this->gridField, $request);
183 |
184 | if (!is_a($form, Form::class)) {
185 | return $form;
186 | }
187 |
188 | $form->makeReadonly();
189 |
190 |
191 | return $controller->customise([
192 | 'Title' => ($this->record && $this->record->exists() ? $this->record->Title : sprintf(_t('GridField.NewRecord', 'New %s'), singleton($this->gridField->getModelClass())->i18n_singular_name())),
193 | 'ItemEditForm' => $form,
194 | ])->renderWith($this->template);
195 | }
196 |
197 | /**
198 | * Renders the edit form
199 | * @param {SS_HTTPRequest} $request Request data
200 | * @return {string} Rendered edit form
201 | */
202 | public function edit($request)
203 | {
204 | $controller = $this->getToplevelController();
205 | $form = $this->ItemEditForm($this->gridField, $request);
206 |
207 | if (!is_a($form, Form::class)) {
208 | return $form;
209 | }
210 |
211 | return $controller->customise([
212 | 'Title' => ($this->record && $this->record->exists() ? $this->record->Title : sprintf(_t('GridField.NewRecord', 'New %s'), singleton($this->gridField->getModelClass())->i18n_singular_name())),
213 | 'ItemEditForm' => $form,
214 | ])->renderWith($this->template);
215 | }
216 |
217 | /**
218 | * Disabled, the front end does not use breadcrumbs to remember the paths
219 | */
220 | public function Breadcrumbs($unlinked = false)
221 | {
222 | return;
223 | }
224 |
225 | public function doSave($data, $form)
226 | {
227 | $new_record = $this->record->ID == 0;
228 | $controller = $this->getToplevelController();
229 | $list = $this->gridField->getList();
230 |
231 | if (!$this->record->canEdit()) {
232 | return $controller->httpError(403);
233 | }
234 |
235 | if (isset($data['ClassName']) && $data['ClassName'] != $this->record->ClassName) {
236 | $newClassName = $data['ClassName'];
237 | // The records originally saved attribute was overwritten by $form->saveInto($record) before.
238 | // This is necessary for newClassInstance() to work as expected, and trigger change detection
239 | // on the ClassName attribute
240 | $this->record->setClassName($this->record->ClassName);
241 | // Replace $record with a new instance
242 | $this->record = $this->record->newClassInstance($newClassName);
243 | }
244 |
245 | try {
246 | $form->saveInto($this->record);
247 | $this->record->write();
248 | $extraData = $this->getExtraSavedData($this->record, $list);
249 | $list->add($this->record, $extraData);
250 | } catch (ValidationException $e) {
251 | $form->setSessionValidationResult($e->getResult());
252 |
253 | $controller->getRequest()->getSession()->set("FormInfo.{$form->FormName()}.data", $form->getData());
254 |
255 | return $controller->redirectBack();
256 | }
257 |
258 | // TODO Save this item into the given relationship
259 |
260 | $link = '"' . htmlspecialchars($this->record->Title, ENT_QUOTES) . '"';
261 | $message = _t(
262 | 'GridFieldDetailForm.Saved',
263 | 'Saved {name} {link}',
264 | [
265 | 'name' => $this->record->i18n_singular_name(),
266 | 'link' => $link,
267 | ]
268 | );
269 |
270 | $form->sessionMessage($message, ValidationResult::TYPE_GOOD, ValidationResult::CAST_HTML);
271 |
272 | if ($new_record) {
273 | return $controller->redirect($this->Link());
274 | } else if ($this->gridField->getList()->byId($this->record->ID)) {
275 | return $controller->redirectBack();
276 | } else {
277 | // Changes to the record properties might've excluded the record from
278 | // a filtered list, so return back to the main view if it can't be found
279 | $noActionURL = $controller->removeAction($data['url']);
280 | $controller->getRequest()->addHeader('X-Pjax', 'Content');
281 | return $controller->redirect($noActionURL, 302);
282 | }
283 | }
284 |
285 | public function doDelete($data, $form)
286 | {
287 | $title = $this->record->Title;
288 | try {
289 | if (!$this->record->canDelete()) {
290 | throw new ValidationException(_t('GridFieldDetailForm.DeletePermissionsFailure', "No delete permissions"), 0);
291 | }
292 |
293 | $this->record->delete();
294 | } catch (ValidationException $e) {
295 | $form->sessionMessage($e->getResult()->message(), ValidationResult::TYPE_ERROR);
296 | return Controller::curr()->redirectBack();
297 | }
298 |
299 | $message = sprintf(_t('GridFieldDetailForm.Deleted', 'Deleted %s %s'), $this->record->i18n_singular_name(), htmlspecialchars($title, ENT_QUOTES));
300 |
301 | $toplevelController = $this->getToplevelController();
302 | if ($toplevelController && $toplevelController instanceof LeftAndMain) {
303 | $backForm = $toplevelController->getEditForm();
304 | $backForm->sessionMessage($message, ValidationResult::TYPE_GOOD);
305 | } else {
306 | $form->sessionMessage($message, ValidationResult::TYPE_GOOD);
307 | }
308 |
309 |
310 | //Remove all requirements
311 | Requirements::clear();
312 |
313 | return $this->customise(['GridFieldID' => $this->gridField->ID()])->renderWith(GridField::class . '_deleted');
314 | }
315 |
316 | /**
317 | * Wrapper for redirectBack()
318 | * @see Controller::redirectBack()
319 | */
320 | public function redirectBack(): HTTPResponse
321 | {
322 | return Controller::curr()->redirectBack();
323 | }
324 | }
325 |
--------------------------------------------------------------------------------
/templates/WebbuildersGroup/FrontEndGridField/Forms/GridField/GridFieldDetailForm.ss:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $Title.XML
5 | <% base_tag %>
6 |
7 | <% require themedCSS(layout) %>
8 | <% require themedCSS(typography) %>
9 | <% require themedCSS(form) %>
10 |
11 |
12 | $Title.XML
13 |
14 | $ItemEditForm
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/templates/WebbuildersGroup/FrontEndGridField/Forms/GridField/GridField_deleted.ss:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $Title.XML
5 |
6 |
7 |
14 |
15 |
--------------------------------------------------------------------------------