-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature: Added ObservableProperty and ObservableList
- Loading branch information
1 parent
6e0f459
commit 7e0120a
Showing
3 changed files
with
650 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,357 @@ | ||
/* | ||
* Copyright (c) 2015, Michael Mitterer (office@mikemitterer.at), | ||
* IT-Consulting and Development Limited. | ||
* | ||
* All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
part of m4d_core; | ||
|
||
enum ListChangeType { | ||
ADD, INSERT, UPDATE, REMOVE, CLEAR | ||
} | ||
|
||
typedef bool UpdateItem<T>(final dom.HtmlElement element, final T item); | ||
|
||
/// Propagated if List changes | ||
class ListChangedEvent<T> { | ||
/// [changetype] shows what changed | ||
final ListChangeType changetype; | ||
|
||
/// [item] is set on ADD, REMOVE and UPDATE | ||
final T item; | ||
|
||
/// [prevItem] is set on UPDATE and defines the old Entry | ||
/// It is also set on INSERT. It defines the previous items a the given index | ||
final T prevItem; | ||
|
||
/// Index in der Original-Liste (_innerList) | ||
final int index; | ||
|
||
ListChangedEvent(this.changetype,{ final T this.item, final this.prevItem, final this.index: -1 }); | ||
} | ||
|
||
/// List that sends [ListChangeEvents] to the listener if this list changes | ||
/// Supported methods: | ||
/// Add, insert, update ([]), remove, clear, removeAll | ||
/// | ||
/// @Component | ||
/// class MyComponent extends MdlTemplateComponent { | ||
/// | ||
/// final ObservableList<RecordsPerDay> records = new ObservableList<RecordsPerDay>( | ||
/// updateCallback: _updateListItem); | ||
/// | ||
/// void _init() { | ||
/// | ||
/// render().then((_) { | ||
/// _bindStoreActions(); | ||
/// }); | ||
/// } | ||
/// | ||
/// void _bindStoreActions() { | ||
/// if(_store == null) { return;} | ||
/// | ||
/// _store.onChange.listen((final DataStoreChangedEvent event) { | ||
/// _updateView(); | ||
/// }); | ||
/// } | ||
/// | ||
/// void _updateView() { | ||
/// | ||
/// if(records.length != _store.records.length) { | ||
/// records.clear(); | ||
/// records.addAll(_store.records); | ||
/// | ||
/// } else { | ||
/// final List<RecordsPerDay> _storeRecords = _store.records; | ||
/// for (int index = 0; index < _storeRecords.length; index++) { | ||
/// | ||
/// // Triggers _updateListItem!!! in MaterialRepeat | ||
/// records[index] = _storeRecords[index]; | ||
/// } | ||
/// } | ||
/// } | ||
/// | ||
/// static bool _updateListItem(final dom.HtmlElement element, final RecordsPerDay item, final RecordsPerDay prevItem) { | ||
/// if(element == null) { | ||
/// return false; | ||
/// } | ||
/// final dom.SpanElement date = element.querySelector(".date"); | ||
/// final dom.SpanElement records = element.querySelector(".records"); | ||
/// date.text = item.date.toString(); | ||
/// records.text = item.records.toString(); | ||
/// return true; | ||
/// } | ||
/// | ||
/// @override | ||
/// final String template = "..."; | ||
/// } | ||
/// | ||
class ObservableList<T> extends ListBase<T> { | ||
final Logger _logger = new Logger('mdlobservable.ObservableList'); | ||
|
||
final List<T> _innerList = new List(); | ||
final List<T> _filterBackup = new List(); | ||
|
||
StreamController<ListChangedEvent<T>> _onChange; | ||
|
||
UpdateItem _updateCallback; | ||
|
||
/// If [updateCallback] is given it will be called from [MaterialRepeat] | ||
/// if the list updates | ||
ObservableList({final UpdateItem updateCallback /*: _defaultUpdateCallback*/ }) | ||
: _updateCallback = updateCallback { | ||
if(_updateCallback == null) { | ||
_updateCallback = _defaultUpdateCallback; | ||
} | ||
} | ||
|
||
/// Propagated Event-Listener | ||
Stream<ListChangedEvent<T>> get onChange { | ||
if(_onChange == null) { | ||
_onChange = new StreamController<ListChangedEvent<T>>.broadcast( | ||
onCancel: () => _onChange = null); | ||
} | ||
return _onChange.stream; | ||
} | ||
|
||
int get length => _innerList.length; | ||
|
||
void set length(int length) { | ||
_innerList.length = length; | ||
} | ||
|
||
/// Updates the internal List. | ||
void operator []=(int index, T value) { | ||
_fire(new ListChangedEvent<T>(ListChangeType.UPDATE, | ||
item: value, | ||
prevItem: _innerList[index], | ||
index: index )); | ||
|
||
_innerList[index] = value; | ||
} | ||
|
||
T operator [](int index) => _innerList[index]; | ||
|
||
void add(final T value) { | ||
_innerList.add(value); | ||
|
||
_fire(new ListChangedEvent<T>( | ||
ListChangeType.ADD, | ||
item: value, | ||
index: _innerList.indexOf(value))); | ||
} | ||
|
||
void addAll(Iterable<T> all) { | ||
_innerList.addAll(all); | ||
all.forEach((final element) { | ||
_fire(new ListChangedEvent<T>( | ||
ListChangeType.ADD, | ||
item: element, | ||
index: _innerList.indexOf(element))); | ||
}); | ||
} | ||
|
||
void addIfAbsent(final T value) { | ||
if(!_innerList.contains(value)) { | ||
add(value); | ||
} | ||
} | ||
|
||
@override | ||
void insert(int index, T element) { | ||
RangeError.checkValueInInterval(index, 0, length, "index"); | ||
|
||
if(index == _innerList.length) { | ||
add(element); | ||
|
||
} else { | ||
if(index == 0) { | ||
|
||
_fire(new ListChangedEvent<T>( | ||
ListChangeType.INSERT, | ||
item: element, | ||
index: index )); | ||
|
||
_innerList.insert(index,element); | ||
|
||
} else { | ||
|
||
_fire(new ListChangedEvent<T>( | ||
ListChangeType.INSERT, | ||
item: element, | ||
prevItem: _innerList[index], | ||
index: index)); | ||
|
||
_innerList.insert(index,element); | ||
} | ||
} | ||
} | ||
|
||
@override | ||
void clear() { | ||
_clearList(); | ||
_clearFilter(); | ||
} | ||
|
||
@override | ||
void removeRange(int start, int end) { | ||
RangeError.checkValidRange(start, end, this.length); | ||
for(int index = start;index < end;index++) { | ||
_fire(new ListChangedEvent<T>( | ||
ListChangeType.REMOVE, | ||
item: _innerList[index], | ||
index: index )); | ||
} | ||
_innerList.removeRange(start,end); | ||
} | ||
|
||
@override | ||
bool remove(final Object element) { | ||
_fire(new ListChangedEvent<T>( | ||
ListChangeType.REMOVE, | ||
item: element as T, | ||
index: _innerList.indexOf(element as T) )); | ||
|
||
return _innerList.remove(element); | ||
} | ||
|
||
@override | ||
void removeWhere(bool test(final T element)) { | ||
final List<T> itemsToRemove = new List<T>(); | ||
|
||
_innerList.forEach((final T element) { | ||
if(test(element)) { | ||
itemsToRemove.add(element); | ||
} | ||
}); | ||
|
||
itemsToRemove.forEach((final T element) => remove(element)); | ||
} | ||
|
||
@override | ||
void retainWhere(bool test(final T element)) { | ||
final List<T> itemsToRemove = new List<T>(); | ||
|
||
_innerList.forEach((final T element) { | ||
// remove items where test fails | ||
if(test(element) == false) { | ||
itemsToRemove.add(element); | ||
} | ||
}); | ||
|
||
itemsToRemove.forEach((final T element) => remove(element)); | ||
} | ||
|
||
/// Filter items in list | ||
/// | ||
/// final String text = filter.value.trim(); | ||
/// | ||
/// if(text.isNotEmpty) { | ||
/// components.filter((final HackintoshComponent element) => element.name.contains(text)); | ||
/// } else { | ||
/// components.resetFilter(); | ||
/// } | ||
/// | ||
void filter(bool test(final T element)) { | ||
if(_filterBackup.isEmpty) { | ||
_filterBackup.addAll(_innerList); | ||
} | ||
|
||
_clearList(); | ||
addAll(_filterBackup.where(test)); | ||
} | ||
|
||
/// Resets the item-List to it's original stand | ||
void resetFilter() { | ||
if(_filterBackup.isNotEmpty) { | ||
_clearList(); | ||
|
||
addAll(_filterBackup); | ||
_filterBackup.clear(); | ||
} | ||
} | ||
|
||
/// Called by MaterialRepeat do make fast updates | ||
bool update(final dom.HtmlElement element, final T item) | ||
=> _updateCallback(element,item); | ||
|
||
/// ADD, INSERT, UPDATE, REMOVE, CLEAR | ||
ObservableList<T> where(bool test(final T element)) { | ||
_logger.info("Where called!!!!!!!!!!!!!!!"); | ||
|
||
final newList = new ObservableList<T>(); | ||
onChange.listen((final ListChangedEvent event) { | ||
if(event.changetype != ListChangeType.CLEAR && test(event.item)) { | ||
// ignore: missing_enum_constant_in_switch | ||
switch(event.changetype) { | ||
case ListChangeType.ADD: | ||
newList.add(event.item); | ||
break; | ||
case ListChangeType.INSERT: | ||
//newList.insert(event.index,event.item); | ||
newList.add(event.item); | ||
|
||
_logger.info("Items1: ${newList.length}"); | ||
break; | ||
// case ListChangeType.UPDATE: | ||
// if(newList.contains(event.item)) { | ||
// //final index = newList. | ||
// newList.update(event., item) | ||
// } | ||
// newList.add(event.item); | ||
// break; | ||
case ListChangeType.REMOVE: | ||
newList.remove(event.item); | ||
break; | ||
} | ||
} | ||
if(event.changetype == ListChangeType.CLEAR) { | ||
newList.clear(); | ||
} | ||
}); | ||
|
||
return newList; | ||
} | ||
|
||
//- private ----------------------------------------------------------------------------------- | ||
|
||
void _fire(final ListChangedEvent<T> event) { | ||
if( _onChange != null && _onChange.hasListener) { | ||
_onChange.add(event); | ||
} | ||
} | ||
|
||
/// Remove all items from list (DOM + innerList) | ||
void _clearList() { | ||
_fire(new ListChangedEvent<T>(ListChangeType.CLEAR)); | ||
_innerList.clear(); | ||
} | ||
|
||
/// Remove items backed up items for filter | ||
void _clearFilter() { | ||
_filterBackup.clear(); | ||
} | ||
|
||
/// Default Callback for updating the UI | ||
/// | ||
/// It returns false which indicates that MaterialRepeater should call it's own | ||
/// update routines | ||
// @ToDo: https://github.com/dart-lang/sdk/issues/28996 | ||
static bool _defaultUpdateCallback/*<T>*/(final dom.HtmlElement element, final /* T */ item) { | ||
return false; | ||
} | ||
|
||
} |
Oops, something went wrong.