Skip to content

Commit 298431a

Browse files
committed
adapter assignments rebuild
1 parent 094c64d commit 298431a

File tree

5 files changed

+183
-125
lines changed

5 files changed

+183
-125
lines changed

src/modules/adapter.js

+169-114
Original file line numberDiff line numberDiff line change
@@ -1,161 +1,214 @@
1-
export default function Adapter($rootScope, $parse, $attr, viewport, buffer, adjustBuffer, element) {
2-
const viewportScope = viewport.scope() || $rootScope;
3-
let disabled = false;
4-
let self = this;
5-
6-
createValueInjector('adapter')(self);
7-
let topVisibleInjector = createValueInjector('topVisible');
8-
let topVisibleElementInjector = createValueInjector('topVisibleElement');
9-
let topVisibleScopeInjector = createValueInjector('topVisibleScope');
10-
let isLoadingInjector = createValueInjector('isLoading');
11-
12-
// Adapter API definition
13-
14-
Object.defineProperty(this, 'disabled', {
15-
get: () => disabled,
16-
set: (value) => (!(disabled = value)) ? adjustBuffer() : null
17-
});
18-
19-
this.isLoading = false;
20-
this.isBOF = () => buffer.bof;
21-
this.isEOF = () => buffer.eof;
22-
this.isEmpty = () => !buffer.length;
23-
24-
this.applyUpdates = (arg1, arg2) => {
1+
function findCtrl(scope, ctrl) {
2+
if (!scope) {
3+
return;
4+
}
5+
if (scope.hasOwnProperty(ctrl) && Object.getPrototypeOf(scope[ctrl]).constructor.hasOwnProperty('$inject')) {
6+
return scope[ctrl];
7+
}
8+
return findCtrl(scope.$parent, ctrl);
9+
}
10+
11+
function assignAttr(attr, scope, element) {
12+
if (!attr || !(attr = attr.replace(/^\s+|\s+$/gm, ''))) {
13+
return;
14+
}
15+
16+
let onSyntax = attr.match(/^(.+)(\s+on\s+)(.+)?/);
17+
let asSyntax = attr.match(/^([^.]+)\.(.+)?/);
18+
19+
if (onSyntax && onSyntax.length === 4) { // controller on (backward compatibility), deprecated since v1.6.1
20+
window.console.warn('Angular ui-scroll adapter assignment warning. "Controller On" syntax has been deprecated since ui-scroll v1.6.1.');
21+
let ctrl = onSyntax[3];
22+
let tail = onSyntax[1];
23+
let candidate = element;
24+
while (candidate.length) {
25+
let candidateScope = candidate.scope(); // doesn't work when debugInfoEnabled flag = true
26+
let candidateName = (candidate.attr('ng-controller') || '').match(/(\w(?:\w|\d)*)(?:\s+as\s+(\w(?:\w|\d)*))?/);
27+
if (candidateName && candidateName[1] === ctrl) {
28+
return {
29+
target: candidateScope,
30+
source: tail
31+
};
32+
}
33+
candidate = candidate.parent();
34+
}
35+
throw new Error('Angular ui-scroll adapter assignment error. Failed to locate target controller "' + ctrl + '" to inject "' + tail + '"');
36+
}
37+
else if (asSyntax && asSyntax.length === 3) { // controller as
38+
let ctrl = asSyntax[1];
39+
let tail = asSyntax[2];
40+
let foundCtrl = findCtrl(scope, ctrl);
41+
if (foundCtrl) {
42+
return {
43+
target: foundCtrl,
44+
source: tail
45+
};
46+
}
47+
}
48+
49+
return {
50+
target: scope,
51+
source: attr
52+
};
53+
}
54+
55+
class Adapter {
56+
57+
constructor(viewport, buffer, adjustBuffer, reload, $attr, $parse, element) {
58+
this.viewport = viewport;
59+
this.buffer = buffer;
60+
this.adjustBuffer = adjustBuffer;
61+
this.reload = reload;
62+
63+
this.publicContext = {};
64+
this.assignAdapter($attr, $parse, element);
65+
this.generatePublicContext($attr, $parse, element);
66+
67+
this.isLoading = false;
68+
this.disabled = false;
69+
}
70+
71+
assignAdapter($attr, $parse, element) {
72+
let data = assignAttr($attr.adapter, this.viewport.getScope(), element);
73+
74+
if (data) {
75+
try {
76+
$parse(data.source).assign(data.target, {});
77+
let adapterOnScope = $parse(data.source)(data.target);
78+
79+
angular.extend(adapterOnScope, this.publicContext);
80+
this.publicContext = adapterOnScope;
81+
}
82+
catch (error) {
83+
error.message = `Angular ui-scroll Adapter assignment exception.\n` +
84+
`Can't parse "${$attr.adapter}" expression.\n` +
85+
error.message;
86+
throw error;
87+
}
88+
}
89+
}
90+
91+
generatePublicContext($attr, $parse, element) {
92+
// these methods will be accessible out of ui-scroll via user defined adapter
93+
const publicMethods = ['reload', 'applyUpdates', 'append', 'prepend', 'isBOF', 'isEOF', 'isEmpty'];
94+
for (let i = publicMethods.length - 1; i >= 0; i--) {
95+
this.publicContext[publicMethods[i]] = this[publicMethods[i]].bind(this);
96+
}
97+
98+
// these read-only props will be accessible out of ui-scroll via user defined adapter
99+
const publicProps = ['isLoading', 'topVisible', 'topVisibleElement', 'topVisibleScope'];
100+
for (let i = publicProps.length - 1; i >= 0; i--) {
101+
let property, assignProp;
102+
let data = assignAttr($attr[publicProps[i]], this.viewport.getScope(), element);
103+
if (data) {
104+
assignProp = $parse(data.source).assign;
105+
}
106+
Object.defineProperty(this, publicProps[i], {
107+
get: () => property,
108+
set: (value) => {
109+
property = value;
110+
if (assignProp) {
111+
assignProp(data.target, value);
112+
}
113+
this.publicContext[publicProps[i]] = value;
114+
}
115+
});
116+
}
117+
118+
// non-read-only public property
119+
Object.defineProperty(this.publicContext, 'disabled', {
120+
get: () => this.disabled,
121+
set: (value) => (!(this.disabled = value)) ? this.adjustBuffer() : null
122+
});
123+
}
124+
125+
loading(value) {
126+
this['isLoading'] = value;
127+
}
128+
129+
isBOF() {
130+
return this.buffer.bof;
131+
}
132+
133+
isEOF() {
134+
return this.buffer.eof;
135+
}
136+
137+
isEmpty() {
138+
return !this.buffer.length;
139+
}
140+
141+
applyUpdates(arg1, arg2) {
25142
if (angular.isFunction(arg1)) {
26143
// arg1 is the updater function, arg2 is ignored
27-
buffer.slice(0).forEach((wrapper) => {
144+
this.buffer.slice(0).forEach((wrapper) => {
28145
// we need to do it on the buffer clone, because buffer content
29146
// may change as we iterate through
30-
applyUpdate(wrapper, arg1(wrapper.item, wrapper.scope, wrapper.element));
147+
this.applyUpdate(wrapper, arg1(wrapper.item, wrapper.scope, wrapper.element));
31148
});
32149
} else {
33150
// arg1 is item index, arg2 is the newItems array
34151
if (arg1 % 1 !== 0) {// checking if it is an integer
35152
throw new Error('applyUpdates - ' + arg1 + ' is not a valid index');
36153
}
37154

38-
const index = arg1 - buffer.first;
39-
if ((index >= 0 && index < buffer.length)) {
40-
applyUpdate(buffer[index], arg2);
155+
const index = arg1 - this.buffer.first;
156+
if ((index >= 0 && index < this.buffer.length)) {
157+
this.applyUpdate(this.buffer[index], arg2);
41158
}
42159
}
43160

44-
adjustBuffer();
45-
};
46-
47-
this.append = (newItems) => {
48-
buffer.append(newItems);
49-
adjustBuffer();
50-
};
161+
this.adjustBuffer();
162+
}
51163

52-
this.prepend = (newItems) => {
53-
buffer.prepend(newItems);
54-
adjustBuffer();
55-
};
164+
append(newItems) {
165+
this.buffer.append(newItems);
166+
this.adjustBuffer();
167+
}
56168

57-
this.loading = (value) => {
58-
isLoadingInjector(value);
59-
};
169+
prepend(newItems) {
170+
this.buffer.prepend(newItems);
171+
this.adjustBuffer();
172+
}
60173

61-
this.calculateProperties = () => {
174+
calculateProperties() {
62175
let item, itemHeight, itemTop, isNewRow, rowTop = null;
63176
let topHeight = 0;
64-
for (let i = 0; i < buffer.length; i++) {
65-
item = buffer[i];
177+
for (let i = 0; i < this.buffer.length; i++) {
178+
item = this.buffer[i];
66179
itemTop = item.element.offset().top;
67180
isNewRow = rowTop !== itemTop;
68181
rowTop = itemTop;
69182
if (isNewRow) {
70183
itemHeight = item.element.outerHeight(true);
71184
}
72-
if (isNewRow && (viewport.topDataPos() + topHeight + itemHeight <= viewport.topVisiblePos())) {
185+
if (isNewRow && (this.viewport.topDataPos() + topHeight + itemHeight <= this.viewport.topVisiblePos())) {
73186
topHeight += itemHeight;
74187
} else {
75188
if (isNewRow) {
76-
topVisibleInjector(item.item);
77-
topVisibleElementInjector(item.element);
78-
topVisibleScopeInjector(item.scope);
189+
this['topVisible'] = item.item;
190+
this['topVisibleElement'] = item.element;
191+
this['topVisibleScope'] = item.scope;
79192
}
80193
break;
81194
}
82195
}
83-
};
84-
85-
// private function definitions
86-
87-
function createValueInjector(attribute) {
88-
let expression = $attr[attribute];
89-
let scope = viewportScope;
90-
let assign;
91-
if (expression) {
92-
// it is ok to have relaxed validation for the first part of the 'on' expression.
93-
// additional validation will be done by the $parse service below
94-
let match = expression.match(/^(\S+)(?:\s+on\s+(\w(?:\w|\d)*))?/);
95-
if (!match)
96-
throw new Error('Expected injection expression in form of \'target\' or \'target on controller\' but got \'' + expression + '\'');
97-
let target = match[1];
98-
let onControllerName = match[2];
99-
100-
let parseController = (controllerName, on) => {
101-
let candidate = element;
102-
while (candidate.length) {
103-
let candidateScope = candidate.scope();
104-
// ng-controller's "Controller As" parsing
105-
let candidateName = (candidate.attr('ng-controller') || '').match(/(\w(?:\w|\d)*)(?:\s+as\s+(\w(?:\w|\d)*))?/);
106-
if (candidateName && candidateName[on ? 1 : 2] === controllerName) {
107-
scope = candidateScope;
108-
return true;
109-
}
110-
// directive's/component's "Controller As" parsing
111-
if (!on && candidateScope && candidateScope.hasOwnProperty(controllerName) && Object.getPrototypeOf(candidateScope[controllerName]).constructor.hasOwnProperty('$inject')) {
112-
scope = candidateScope;
113-
return true;
114-
}
115-
candidate = candidate.parent();
116-
}
117-
};
118-
119-
if (onControllerName) { // 'on' syntax DOM parsing (adapter="adapter on ctrl")
120-
scope = null;
121-
parseController(onControllerName, true);
122-
if (!scope) {
123-
throw new Error('Failed to locate target controller \'' + onControllerName + '\' to inject \'' + target + '\'');
124-
}
125-
}
126-
else { // try to parse DOM with 'Controller As' syntax (adapter="ctrl.adapter")
127-
let controllerAsName;
128-
let dotIndex = target.indexOf('.');
129-
if (dotIndex > 0) {
130-
controllerAsName = target.substr(0, dotIndex);
131-
parseController(controllerAsName, false);
132-
}
133-
}
134-
135-
assign = $parse(target).assign;
136-
}
137-
return (value) => {
138-
if (self !== value) // just to avoid injecting adapter reference in the adapter itself. Kludgy, I know.
139-
self[attribute] = value;
140-
if (assign)
141-
assign(scope, value);
142-
};
143196
}
144197

145-
function applyUpdate(wrapper, newItems) {
198+
applyUpdate(wrapper, newItems) {
146199
if (!angular.isArray(newItems)) {
147200
return;
148201
}
149202

150203
let keepIt;
151-
let pos = (buffer.indexOf(wrapper)) + 1;
204+
let pos = (this.buffer.indexOf(wrapper)) + 1;
152205

153206
newItems.reverse().forEach((newItem) => {
154207
if (newItem === wrapper.item) {
155208
keepIt = true;
156209
pos--;
157210
} else {
158-
buffer.insert(pos, newItem);
211+
this.buffer.insert(pos, newItem);
159212
}
160213
});
161214

@@ -164,4 +217,6 @@ export default function Adapter($rootScope, $parse, $attr, viewport, buffer, adj
164217
}
165218
}
166219

167-
}
220+
}
221+
222+
export default Adapter;

src/modules/viewport.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import Padding from './padding';
22

3-
export default function Viewport(elementRoutines, buffer, element, viewportController, padding) {
3+
export default function Viewport(elementRoutines, buffer, element, viewportController, $rootScope, padding) {
44
let topPadding = null;
55
let bottomPadding = null;
66
const viewport = viewportController && viewportController.viewport ? viewportController.viewport : angular.element(window);
77
const container = viewportController && viewportController.container ? viewportController.container : undefined;
8+
const scope = viewportController && viewportController.scope ? viewportController.scope : $rootScope;
89

910
viewport.css({
1011
'overflow-anchor': 'none',
@@ -17,6 +18,10 @@ export default function Viewport(elementRoutines, buffer, element, viewportContr
1718
}
1819

1920
angular.extend(viewport, {
21+
getScope() {
22+
return scope;
23+
},
24+
2025
createPaddingElements(template) {
2126
topPadding = new Padding(template);
2227
bottomPadding = new Padding(template);

src/ui-scroll-grid.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ angular.module('ui.scroll.grid', [])
112112
let rowMap = new Map();
113113

114114
$timeout(() => {
115-
scrollViewport.adapter.gridAdapter = new GridAdapter(this);
115+
scrollViewport.adapter.publicContext.gridAdapter = new GridAdapter(this);
116116
scrollViewport.adapter.transform = (scope, item) => transform(rowMap.get(scope), item);
117117
});
118118

src/ui-scroll.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ angular.module('ui.scroll', [])
2020
function (scope, element) {
2121
this.container = element;
2222
this.viewport = element;
23+
this.scope = scope;
2324

2425
angular.forEach(element.children(), (child => {
2526
if (child.tagName.toLowerCase() === 'tbody') {
@@ -79,8 +80,8 @@ angular.module('ui.scroll', [])
7980

8081
let elementRoutines = new ElementRoutines($injector, $q);
8182
let buffer = new ScrollBuffer(elementRoutines, bufferSize);
82-
let viewport = new Viewport(elementRoutines, buffer, element, viewportController, padding);
83-
let adapter = new Adapter($rootScope, $parse, $attr, viewport, buffer, adjustBuffer, element);
83+
let viewport = new Viewport(elementRoutines, buffer, element, viewportController, $rootScope, padding);
84+
let adapter = new Adapter(viewport, buffer, adjustBuffer, reload, $attr, $parse, element);
8485

8586
if (viewportController) {
8687
viewportController.adapter = adapter;
@@ -140,8 +141,6 @@ angular.module('ui.scroll', [])
140141
}, success);
141142
};
142143

143-
adapter.reload = reload;
144-
145144
/**
146145
* Build padding elements
147146
*

0 commit comments

Comments
 (0)