dojo.provide("epages.cartridges.de_epages.content.widget.TagEditor"); dojo.require("dijit.form.ComboBox"); dojo.require("epages.widget.FormElement"); dojo.require("dojo.data.util.simpleFetch"); dojo.require("dojo.data.util.filter"); dojo.require("dojo.regexp"); parent.epages.cartridges.de_epages.content.widget.valueGrouped = {}; // collection of all selected tags in TagEditors grouped by a groupname dojo.declare( "epages.cartridges.de_epages.content.widget._TagEditorMenu", dijit.form._ComboBoxMenu, { templateString: "<ul class='dijitReset dijitMenu dijitTagEditor' style='background-color:#FFF;' dojoAttachEvent='onmousedown:_onMouseDown,onmouseup:_onMouseUp,onmouseover:_onMouseOver,onmouseout:_onMouseOut' tabIndex='-1' style='overflow: \"auto\"; overflow-x: \"hidden\";'>" +"<li class='dijitMenuPreviousButton' dojoAttachPoint='previousButton' waiRole='option'></li>" +"<li class='dijitMenuNextButton' dojoAttachPoint='nextButton' waiRole='option'></li>" +"</ul>", itemClasses : "dijitReset", createOptions: function(results, dataObject, labelFunc){ // summary: // Fills in the items in the drop down list // results: // Array of dojo.data items // dataObject: // dojo.data store // labelFunc: // Function to produce a label in the drop down list from a dojo.data item //this._dataObject=dataObject; //this._dataObject.onComplete=dojo.hitch(comboBox, comboBox._openResultList); // display "Previous . . ." button this.previousButton.style.display = (dataObject.start == 0) ? "none" : ""; dojo.attr(this.previousButton, "id", this.id + "_prev"); // create options using _createOption function defined by parent // ComboBox (or FilteringSelect) class // #2309: // iterate over cache nondestructively dojo.forEach(results, function(item, i){ var menuitem = this._createOption(item, labelFunc); menuitem.className = this.itemClasses; dojo.attr(menuitem, "id", this.id + i); this.domNode.insertBefore(menuitem, this.nextButton); }, this); // display "Next . . ." button var displayMore = false; //Try to determine if we should show 'more'... if(dataObject._maxOptions && dataObject._maxOptions != -1){ if((dataObject.start + dataObject.count) < dataObject._maxOptions){ displayMore = true; }else if((dataObject.start + dataObject.count) > (dataObject._maxOptions - 1)){ //Weird return from a datastore, where a start + count > maxOptions // implies maxOptions isn't really valid and we have to go into faking it. //And more or less assume more if count == results.length if(dataObject.count == results.length){ displayMore = true; } } }else if(dataObject.count == results.length){ //Don't know the size, so we do the best we can based off count alone. //So, if we have an exact match to count, assume more. displayMore = true; } this.nextButton.style.display = displayMore ? "" : "none"; dojo.attr(this.nextButton,"id", this.id + "_next"); return this.domNode.childNodes; } } ); dojo.declare( "epages.cartridges.de_epages.content.widget.TagEditor", dijit.form.ComboBox, { templateString: '', templatePath: dojo.moduleUrl('epages.cartridges.de_epages.content.widget',"templates/TagEditor.html"), pageSize : 5, // old numberOfEntries /** * public properties */ // this can be used to group multiple TagEditors on the same page // if a tag is added, then all TagEditors in the same group will // be updated with the new tag group: "", // string - name of all tag inputs which share the same tags presetTags: "", // string - use this to preset Tags, seperate with "," value: "", // string[] name: "Tags", // string delimiter: ',', // string - tagseperator placeholder: '', description: '', // string // hasDownArrow: [const] Boolean // Set this textbox to have a down arrow button, to display the drop down list. // Defaults to true. hasDownArrow: false, // searchDelay: Integer // Delay in milliseconds between when user types something and we start // searching based on that value searchDelay: 100, style: '', labelType: "html", storeType : "epages.cartridges.de_epages.content.widget._TagEditorStore", /** * private properties */ _editAtLastEl : true, // is the user edition the last tag in the taglist _activeTagIndex: null, // current working tag postMixInProperties: function() { this.store = {"dummy":"dummy"}; this.inherited("postMixInProperties",arguments); this.baseClass=""; }, postCreate: function() { // summary: create store, read preset tags, set input value, subscribe events // needs to be done BEFORE inherited // read preset tags if (typeof(this.presetTags) == "string") { this.presetTags = this._splitTags(this.presetTags); } else { this.presetTags = $A(); } // add preset to value group var valueGrouped = parent.epages.cartridges.de_epages.content.widget.valueGrouped; // added compatiblity to previous tageditor if(dojo.isArray(valueGrouped[this.group])){ this.presetTags.merge(valueGrouped[this.group]); valueGrouped[this.group] = undefined; } // create tag source if (valueGrouped[this.group] === undefined) { valueGrouped[this.group] = new(eval(this.storeType))(this.id); } if (this.presetTags.length) { valueGrouped[this.group].mergeIn(this.presetTags); } if (typeof(this.value) == "string") { this.value = this._splitTags(this.value); } this.store = valueGrouped[this.group]; // remove dojo magic, someone somewhere deep in dojo sets an id on the input this.focusNode.removeAttribute("id"); this.focusNode = new epages.widget.FormElement({}, this.focusNode).elementNode; if (this.delimiter) { this.focusNode.value = this.value.join(this.delimiter + ' '); } else if(this.value.length){ this.focusNode.value = this.value[0]; } else { this.focusNode.value = ""; } this.inherited("postCreate",arguments); if(this.description.length > 1) { this.focusNode.setAttribute("description", this.description); } // this._updatevalue(); this.connect(this.focusNode, "onkeyup", "_evaluateInput"); epages.topDojo.subscribe('Createobjectbutton/objectCreated',this,"hide"); epages.topDojo.subscribe('Createobjectbutton/closed',this,"hide"); dojo.forEach(dojo.query('.dijitPlaceHolder',this.domNode),function(item){ dojo.destroy(item); }); }, _getQueryString: function(/*String*/ text){ // overwrite dijit.form.ComboBoxMixin // summary: add trim return dojo.string.trim(dojo.string.substitute(this.queryExpr, [text])); }, _showResults: function(/*String*/ key,/*Sting?*/lastTyped){ // - changed popup class // - set _activeTagIndex, _editAtLastEl if (this.delimiter) { var tagList = this.focusNode.value; var caretPosition = this._getCaretPos(this.focusNode); var countTags = tagList.substr(0, caretPosition).match(new RegExp(this.delimiter, 'g')); this._activeTagIndex = countTags ? countTags.length : 0; if (!tagList.substr(caretPosition).match(new RegExp(this.delimiter, 'g'))) { this._editAtLastEl = true; } else { this._editAtLastEl = false; } } if(!this._popupWidget){ // create the suggest popup var popupId = this.id + "_popup"; this._popupWidget = new epages.cartridges.de_epages.content.widget._TagEditorMenu({ onChange: dojo.hitch(this, function(evt){ this._selectOption(evt); if (this.delimiter && evt.target != this._popupWidget.nextButton && evt.target != this._popupWidget.previousButton && this._editAtLastEl) { // if last tag is edited and tag is selected add delimitter this.focusNode.value = this.focusNode.value + this.delimiter+" "; } }), id: popupId }); dijit.removeWaiState(this.focusNode,"activedescendant"); dijit.setWaiState(this.focusNode,"owns",popupId); // associate popup with focusNode } // create a new query to prevent accidentally querying for a hidden // value from FilteringSelect's keyField var query = dojo.clone(this.query); // #5970 this._lastInput = lastTyped ? lastTyped : key; // Store exactly what was entered by the user. this._lastQuery = query[this.searchAttr] = this._getQueryString(key); // #5970: set _lastQuery, *then* start the timeout // otherwise, if the user types and the last query returns before the timeout, // _lastQuery won't be set and their input gets rewritten this.searchTimer=setTimeout(dojo.hitch(this, function(query, _this){ this.searchTimer = null; var fetch = { queryOptions: { ignoreCase: this.ignoreCase, deep: true }, query: query, onBegin: dojo.hitch(this, "_setMaxOptions"), onComplete: dojo.hitch(this, "_openResultList"), onError: function(errText){ _this._fetchHandle = null; console.error('dijit.form.ComboBox: ' + errText); dojo.hitch(_this, "_hideResultList")(); }, start: 0, count: this.pageSize, origKey: key, tagEditorId: this.id // exclude tags of own id }; dojo.mixin(fetch, _this.fetchProperties); this._fetchHandle = _this.store.fetch(fetch); var nextSearch = function(dataObject, direction){ dataObject.start += dataObject.count*direction; // #4091: // tell callback the direction of the paging so the screen // reader knows which menu option to shout dataObject.direction = direction; this._fetchHandle = this.store.fetch(dataObject); }; this._nextSearch = this._popupWidget.onPage = dojo.hitch(this, nextSearch, this._fetchHandle); }, query, this), this.searchDelay); }, _startSearch: function(/*String*/ key,/*Sting?*/lastTyped){ // overwrite dijit.form.ComboBoxMixin this._showResults(key,lastTyped); }, _announceOption: function(/*Node*/ node){ // summary: // a11y code that puts the highlighted option in the focusNode. // This way screen readers will know what is happening in the // menu. // overwrite dijit.form.ComboBoxMixin if(!node){ return; } // pull the text value from the item attached to the DOM node var newValue; if( node == this._popupWidget.nextButton || node == this._popupWidget.previousButton){ this.item = undefined; this.value = ''; }else{ newValue = this.inputLabelFunc(node.item, this.store); // get the text that the user manually entered (cut off autocompleted text) this.focusNode.value = this.focusNode.value.substring(0, this._lastInput.length); // set up ARIA activedescendant dijit.setWaiState(this.focusNode, "activedescendant", dojo.attr(node, "id")); // autocomplete the rest of the option to announce change this._autoCompleteText(newValue); } }, _autoCompleteText: function(/*String*/ text){ // overwrite dijit.form.ComboBoxMixin var fn = this.focusNode; // IE7: clear selection so next highlight works all the time var oldCpos = this._getCaretPos(fn); dijit.selectInputText(fn, fn.value.length); // does text autoComplete the value in the focusNode? var newCpos = this._getCaretPos(fn); // only try to extend if we added the last character at the end of the input if((newCpos+1) > fn.value.length){ // only add to input node as we would overwrite Capitalisation of chars // actually, that is ok var tagList = fn.value; if (this.delimiter) { var tlSplit = tagList.split(this.delimiter); if(tlSplit.length>this._activeTagIndex){ // replace old with new tag but keep leading and trailing spaces tlSplit[this._activeTagIndex] = tlSplit[this._activeTagIndex].replace(/^(\s*).*(\s*)$/,"$1"+text+"$2"); // calculate chars to select var selectEnd = tlSplit.slice(0,this._activeTagIndex+1).join(this.delimiter).length; fn.value = tlSplit.join(this.delimiter); dijit.selectInputText(fn, oldCpos,selectEnd); } } else { fn.value = tagList.replace(/^(\s*).*(\s*)$/,"$1"+text+"$2"); dijit.selectInputText(fn, oldCpos); } } }, _startSearchFromInput: function(){ // overwrite dijit.form.ComboBoxMixin // fix for multi tag // calculate tag which is currently under edit var tagList = this.focusNode.value; var caretPosition = this._getCaretPos(this.focusNode); var tag = this._getTagAt(caretPosition, tagList); this._startSearch(tag.replace(/([\\\*\?])/g, "\\$1"),this.focusNode.value.replace(/([\\\*\?])/g, "\\$1")); }, _startSearchAll: function(){ // overwrite dijit.form.ComboBoxMixin // fix for multi tag this._startSearch('',this.focusNode.value.replace(/([\\\*\?])/g, "\\$1")); }, _getTagAt: function(/*int*/position, /*string*/tagList) { // summary: recieve the tag from the position of the caret // return string if (this.delimiter) { var leftPos = tagList.substr(0, position).lastIndexOf(this.delimiter); leftPos = (leftPos == -1 ? 0 : leftPos + 1); var rightPos = tagList.substr(position).indexOf(this.delimiter); rightPos = (rightPos == -1 ? tagList.length : rightPos + position); return tagList.substring(leftPos, rightPos); } else { return tagList; } }, _evaluateInput: function(evt) { // summary: handle key input this._updatevalue(); if (this.delimiter && evt.keyCode == dojo.keys.ENTER && this._editAtLastEl) { // add delimiter if edited tag was the last one this.focusNode.value = this.focusNode.value + this.delimiter+" "; } }, _updatevalue: function() { // summary: set the current value to make it accessable this.value = this._splitTags(this.focusNode.value); this.store.setValue(this.id, this.value); dojo.publish(this.id + "/onChange", [{ tags: this.value }]); }, _splitTags: function(/*String*/tagList) { // summary: split and trim tagList on delimiter // return $A var finalTagArray = []; if (this.delimiter) { var tagArray = tagList.split(this.delimiter); for( var i=0,iLength=tagArray.length ; i<iLength ; i++ ) { var el = tagArray[i]; el = el.replace(/^\s*/, ""); el = el.replace(/\s*$/, ""); if (el != "") { finalTagArray.push(el); } } } else { tagList = tagList.replace(/^\s*/, ""); tagList = tagList.replace(/\s*$/, ""); if (tagList != "") { finalTagArray.push(tagList); } } return $A(finalTagArray); }, getValue: function() { return this.focusNode.value; }, setValue: function(newValue) { if(newValue) { this.value = this.focusNode.value = newValue; } else { this.value = this.focusNode.value = ''; } }, hide: function(){ this._hideResultList(); }, labelFunc: function(/*item*/ item, /*dojo.data.store*/ store){ var itemvalue = store.getValue(item, this.searchAttr); var result; if(typeof(itemvalue) == 'object') { result = itemvalue.labelvalue; } else { result = itemvalue; // String } // beautify if(this.focusNode.value) { var fnv = this.focusNode.value; // 'de' var regex = new RegExp(fnv, "g"); result = result.replace(regex, '<strong>'+fnv+'</strong>'); // 'de_asdasfdasdf' } return result; }, inputLabelFunc: function(/*item*/ item, /*dojo.data.store*/ store){ var itemvalue = store.getValue(item, this.searchAttr); if(typeof(itemvalue) == 'object') { return itemvalue.inputvalue; } return itemvalue; // String } } ); dojo.declare("epages.cartridges.de_epages.content.widget._TagEditorStore", null, { availableTags: $A(), currentValues: {}, constructor: function(){}, mergeIn : function(/*String[]E*/tags){ // summary: add tags to preset tags this.availableTags.merge(tags); this.availableTags.unique(); }, setValue : function(/*String*/id,/*String[]*/tags){ // summary: set value of one of the tageditors of the group if (tags.length) { // prevent empty tags this.currentValues[id] = tags; } }, getValue: function( /* item */ item, /* attribute-name-string */ attribute, /* value? */ defaultValue){ return item; }, isItemLoaded: function(/* anything */ something){ return true; }, getFeatures: function(){ return {"dojo.data.api.Read": true, "dojo.data.api.Identity": true}; }, _fetchItems: function( /* Object */ args, /* Function */ findCallback, /* Function */ errorCallback){ // summary: // See dojo.data.util.simpleFetch.fetch() if(!args.query){ args.query = {}; } if(!args.query.name){ args.query.name = ""; } if(!args.queryOptions){ args.queryOptions = {}; } // merge preset tags and tags from the editors var tags = $A(); for(var v in this.currentValues){ if (v != args.tagEditorId) { tags.merge(this.currentValues[v]); } } tags.merge(this.availableTags); tags.unique(); var matcher = dojo.data.util.filter.patternToRegExp(args.query.name, args.queryOptions.ignoreCase), items = tags.match(matcher); if(args.sort){ items.sort(dojo.data.util.sorter.createSortFunction(args.sort, this)); } findCallback(items, args); }, close: function(/*dojo.data.api.Request || args || null */ request){ return; }, getLabel: function(/* item */ item){ return item.innerHTML; }, getIdentity: function(/* item */ item){ return item; } }); //Mix in the simple fetch implementation to this class. dojo.extend(epages.cartridges.de_epages.content.widget._TagEditorStore,dojo.data.util.simpleFetch);