Giter VIP home page Giter VIP logo

yii2-taggable-behavior's Introduction

Taggable Behavior for Yii 2

Latest Version Software License Build Status Coverage Status Quality Score Total Downloads

This extension provides behavior functions for tagging.

Installation

The preferred way to install this extension is through composer.

Either run

$ composer require 2amigos/yii2-taggable-behavior:~1.0

or add

"2amigos/yii2-taggable-behavior": "~1.0"

to the require section of your composer.json file.

Configuring

First you need to configure model as follows:

use dosamigos\taggable\Taggable;

class Tour extends ActiveRecord
{
    public function behaviors() {
        return [
            [
                'class' => Taggable::className(),
            ],
        ];
    }
}

Usage

First you need to create a tbl_tag (you can choose the name you wish) table with the following format, and build the correspondent ActiveRecord class (i.e. Tag):

+-----------+
|  tbl_tag  |
+-----------+
| id        |
| frequency |
| name      |
+-----------+

After, if you wish to link tags to a certain ActiveRecord (lets say Tour), you need to create the table that will link the Tour Model to the Tag:

+-------------------+
| tbl_tour_tag_assn |
+-------------------+
| tour_id           |
| tag_id            |
+-------------------+

Next, we need to configure the relationship with Tour:

/**
 * @return \yii\db\ActiveQuery
 */
public function getTags()
{
    return $this->hasMany(Tag::className(), ['id' => 'tag_id'])->viaTable('tbl_tour_tag_assn', ['tour_id' => 'id']);
}

Its important to note that if you use a different name, the behavior's $relation attribute should be changed accordingly.

Finally, setup the behavior, and the attribute + rule that is going to work with it in our Tour class, on this case we are going to use defaults tagNames:

/**
 * @inheritdoc
 */
public function rules()
{
    return [
        // ...
        [['tagNames'], 'safe'],
        // ...
    ];
}

/**
 * @inheritdoc
 */
public function behaviors()
{
    return [
        // for different configurations, please see the code
        // we have created tables and relationship in order to
        // use defaults settings
        Taggable::className(),
    ];
}

Thats it, we are now ready to use tags with our model. For example, this is how to use it in our forms together with our Selectize Widget:

// On TagController (example)
// actionList to return matched tags
public function actionList($query)
{
    $models = Tag::findAllByName($query);
    $items = [];

    foreach ($models as $model) {
        $items[] = ['name' => $model->name];
    }
    // We know we can use ContentNegotiator filter
    // this way is easier to show you here :)
    Yii::$app->response->format = Response::FORMAT_JSON;

    return $items;
}


// On our form
<?= $form->field($model, 'tagNames')->widget(SelectizeTextInput::className(), [
    // calls an action that returns a JSON object with matched
    // tags
    'loadUrl' => ['tag/list'],
    'options' => ['class' => 'form-control'],
    'clientOptions' => [
        'plugins' => ['remove_button'],
        'valueField' => 'name',
        'labelField' => 'name',
        'searchField' => ['name'],
        'create' => true,
    ],
])->hint('Use commas to separate tags') ?>

As you can see, tagNames is the attribute (by default) from which we can access our tags and they are stored in it as names separated by commas if you defined your attribute tagNames as string or null, if you define tagNames as an array, it will be filled with the related tags.

Once you post a form with the above field, the tags will be automatically saved and linked to our Tour model.

Testing

$ ./vendor/bin/phpunit

Contributing

Please see CONTRIBUTING for details.

Credits

License

The BSD License (BSD). Please see License File for more information.


web development has never been so fun
www.2amigos.us

yii2-taggable-behavior's People

Contributors

creocoder avatar evgen1986 avatar katag9k avatar kurpievski avatar radzserg avatar tonydspaniard avatar webinvader avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

yii2-taggable-behavior's Issues

problem in update page

hi
thank you for great job
i use last version and create page is ok but in update page init value dont show
but in version 0.1.0 is ok
where is problem?

not working in modal window

Hi,

I have a modal windows with kartik active form.

If I open & load the modal window inlcuding this extension, modal will not be shown and the layout (css) is getting false.

Andy ideas?

Toby

Is it possible to use it more than one time in the same form ?

Hi,
I am trying to use the same concept of tags for categories.

I have Books with Tags and Categories (in the categories i call the name field title)

I duplicate all the tags for categories but i can't manage to load the current categories or save when I update the record.

The tags work perfect and the categories loads, filter, add etc but doesn't save

What I am a bit confuse it is with the tagNames attribute
$tabBook .= $form->field($model, 'tagNames')->widget(SelectizeTextInput::className(), [

because flowing the sample I only add the rule in my book model to make it work:
[['tagNames'], 'safe'],

I tried to do the same with:
[['bookCategories'], 'safe'],

but of course I get an error because bookCategories it is not defined but neither tagName.

The main problem it is when i try to get the array with the categories in the book model:

public function getBookCategories() {
        $esto = $this->hasMany(BookCategory::className(), ['id' => 'category_id'])->viaTable('book_category_assn', ['book_id' => 'id']);
        return $esto;       
}

Do you think i need to create a $bookCategories and a function in the module to save and load the saved categories? Or it is a way to reuse this functionality from the extension?

Thanks a lot

Selectize out of sync with Taggable

Your documentation for this behavior seems to fallen behind your Selectize extension. Specifically, the instructions suggest using $form->field($model, 'tagNames')->widget(Selectize::className(), which doesn't appear to exist any more. I used $form->field($model, 'tagNames')->widget(SelectizeTextInput::className(), which appears to work fine.

I reported earlier an issue about the fact that the tag names don't appear when you open _form.php for update (hence you can't make changes to the assigned tags) nor when using 'tagNames' as a GridView column attribute in index.php, so you can't even see what tags are assigned without going to the database table to view them. This issue doesn't appear to have been fixed. It's true that, by doing as you suggested before and using version 0.1.0 of the extension does resolve the issue, but now I don't know when to actually update the extension?

Any idea when you might update this extension so that we can use the current version.

Need more expansion

like

tbl_tag

id
type // new field
name
frequency

tbl_post_tag_assn

tag_id
post_id

tbl_product_tag_assn

tag_id
product_id

in behaviors

          [
                'class' => Taggable::className(),
                'attribute' => '_{TypeName}',
                'relation' => '{TypeName}',
                'name' => 'id',
                'asArray' => true,
                'type'=>Tag::TYPE_NAME // new config
            ],
          [
                'class' => Taggable::className(),
                'attribute' => '_{TypeName2}',
                'relation' => '{TypeName2}',
                'name' => 'id',
                'asArray' => true,
                'type'=>Tag::TYPE_NAME2 // new config
            ],
 ..............

relation function

public function get{TypeName}(){
      return $this->hasMany(Tag::className(), ['id' => 'tag_id'])->andOnCondition(['type' => Tag::TYPE_NAME])->viaTable('{{%post_tag}}', ['post_id' => 'id']);
}

In form

            <?= $form->field($model, '_typeName')->inline(true)->checkboxList(Tag::getAllTags(Tag::TYPE_NAME)) ?>

            <?= $form->field($model, '_typeName2')->inline(true)->checkboxList(Tag::getAllTags(Tag::TYPE_NAME2)) ?>

Load tagNames with lazyLoad

The issue is we find tagNames in any case. Whether we need it or not. And this is absolutely inefficient.

Moreover I can assume that in most cases we don't need tag assigns. (For example I have profiles that I used on each page and request and profile interests(done via taggable) that I use only on one page.) And I think this stats will be quite similar for other business models.

On other hand if I find N rows with one request.

MyModel::find()->where(['id > 1 AND id < 100'])->all(); 

Then I have N more queries to get tags. That actually don't need me on this page.

 /**
     * @param Event $event
     */
    public function afterFind($event)
    {
        $items = [];
        foreach ($this->owner->{$this->relation} as $tag) {
            $items[] = $tag->{$this->name};
        }
        $this->owner->{$this->attribute} = is_array($this->owner->{$this->attribute})
            ? $items
            : implode(', ', $items);
    }

Since $this->attribute is variable we need to add some magic here and replace this method by that one.

    /**
     * @inheritdoc
     */
    public function canGetProperty($name, $checkVars = true)
    {
        if ($name == $this->attribute) {
            return true;
        }
        return parent::canGetProperty($name, $checkVars);
    }

    /**
     * @inheritdoc
     */
    public function __get($name)
    {
        if ($name == $this->attribute) {
            return $this->getTagNames();
        }

        return parent::__get($name);
    }

    /**
     * @inheritdoc
     */
    public function canSetProperty($name, $checkVars = true)
    {
        if ($name == $this->attribute) {
            return true;
        }
        return parent::canSetProperty($name, $checkVars);
    }


    /**
     * @inheritdoc
     */
    public function __set($name, $value)
    {
        if ($name == $this->attribute) {
            $this->tagValues = $value;      // then we will use behavior attribute vs model attribute to save tags
            return ;
        }

        parent::__set($name, $value);
    }

    /**
     * @inheritdoc
     */
    private function getTagNames()
    {
        $items = [];
        foreach ($this->owner->{$this->relation} as $tag) {
            $items[] = $tag->{$this->name};
        }

        return $this->asArray ? $items : implode(', ', $items);
    }

More complicated but much more efficient.

Unfortunately there is a big BUT. We ask developers to declare MyModel->tagNames as attribute of the model. In this case we need to delete it. In order to load it dynamically via behavior.

Tags Not Being Saved

I'm implemented the Taggable Behavior extension in the past, and it worked great, but now, for some reason, I am unable to get it to work. I'm not sure if it's because of some change in the extension or in th framework.

I've actually created a new Yii2 project (advanced template) and literally copied and pasted the code from your instructions, and it still doesn't work. The only error that shows up in runtime/logs/app.log that may point to the problem is

2015-02-09 11:13:07 [127.0.0.1][-][-][error][yii\base\ErrorException:1] exception 'yii\base\ErrorException' with message 'Call to undefined method common\models\Tag::findAllByName()' in L:\xampp\htdocs\yii2-test\backend\controllers\TagController.php:33
Stack trace:
#0 [internal function]: yii\base\ErrorHandler->handleFatalError()
#1 {main}
2015-02-09 11:13:07 [127.0.0.1][-][-][info][application] $_GET = [
    'r' => 'tag/list'
    'query' => 'no'
]

If that's not the issue, then I don't even know where to start as the rest of the model saves just fine. Can you give me any guidance on this?

Correct db structure to delete junction table entries on delete

Should the extension handle deleting the junction table values when you delete a tag?

I've tried various foreign keys/cascade etc but when a tag is deleted I either get an error or the deleted tag values are still in the junction table.

What's the best way to clean up when a tag is deleted?

Additional attributes

Hello! Thanks for the great extension.
Is it possible to add extra value same way as "name" of the tag while adding(creating) new tags?
I have a data-alias attribute attached to all my tags, also, there is a column "alias" in my DB table 'tag'. How can I put it in while saving a model?

$model->tagNames not populated on update

Having gotten to the point where I can save tags with the model, now when I open the update view for the model, the $model->tagNames field is not populated. I've poked around in your model and also don't find the code to add/remove/leave alone tags on update, but I may have missed it. Is this functionality build into your extension? If not, is this a feature you can add?

Documentation Needed

Could you possibly add some documentation to this behavior. What should the data table schema look like? What does the data entry field on the view _form look like? How do you get it to store the frequency/count in the table? How do you use it to build a tag cloud? (It is, after all, all about tagging.) How would the search form look?

Tags are only saved when beforeSave() method is present

After the last update, my tags didn't save anymore.

I debugged it and found out that the tagValues property in the Taggable class was null.

For further debugging I added the beforeSave() method to the model implementing the TaggableBehavior, and then it worked again.

Is it for some reason neccessary to do that?

Invalid argument supplied for foreach()

I really appreciate your extension. It works beautifully, until you try to retrieve the tags. When I do that, I get a Invalid argument supplied for foreach() error deriving from the afterFind on line 61 in Taggable.php. I'm probably doing something wrong, but I can't figure out what it could be. Any ideas?

Dropdown behaviour inconsistent & incorrect tags displayed

When using a form, the input field for tags is populated correctly. However, clicking in the input field does not display the available tags. Only once I begin to type do any matching tags appear in a dropdown.

If I then delete the typed characters without creating a new tag, then move focus away from the input field, then click back in the input field, the dropdown immediately appears with the same matched tags displayed, even though I haven't typed anything yet.

This means that other tags that don't match are not displayed, misleading other users as they assume the displayed tags are now all of them instead of just those that matched text typed earlier (which was deleted from the input field).

My "SelectizeTextInput" widget is configured as follows:

delimiter => ',', 'create' => true, 'persist' => true, 'createOnBlur' => false, 'openOnFocus' => true, 'preload' => true

A dirty solution I've found is to edit InputWidget.php in the Selectize widget (I'm using the Taggable behaviour) as follows:

Around line 47:
change
$this->clientOptions['load'] = new JsExpression("function (query, callback) { if (!query.length) return callback(); $.getJSON('$url', { query: encodeURIComponent(query) }, function (data) { callback(data); }).fail(function () { callback(); }); }");

to
$this->clientOptions['load'] = new JsExpression("function (query, callback) { $.getJSON('$url', { query: encodeURIComponent(query) }, function (data) { callback(data); }).fail(function () { callback(); }); }");

By simply removing the check for the presence of the query, the dropdown appears every time I click in the input field, displaying all available tags. As soon as I start typing, the displayed list narrows to match the text. I believe the check for the query length interferes with the "preload" configuration option of Selectize, which is what I'm trying to use to ensure the intial single-click in the input field opens a dropdown with all tags available.

This is the behaviour I was expecting from the Taggable / Selectize setup, but maybe I've just got it all wrong :)

I've also found an issue in "Taggable.php" around line 113:
return $this->asArray ? $items : implode(', ', $items);

This should be changed to this:
return $this->asArray ? $items : implode(',', $items);

Basically, removing the extra space after the comma between the single-quotes - this interferes with the pattern matching, causing some tags to be displayed when they shouldn't, and others not to display because they don't match.

By the way, you guys build awesome controls ... please keep up the great work :)

Update relation of the model with new tags

The issue is following after we find the model
Taggable::afterFind will populate $model->tags relation. Then during update
Taggable behavior will change tags assigns but $model->tags will be untouchable.

The fix could be like this
Taggable::afterSave

        $updatedTags = [];
        foreach ($names as $name) {
            $tag = $class::findOne([$this->name => $name]);

            if ($tag === null) {
                $tag = new $class();
                $tag->{$this->name} = $name;
            }

            $tag->{$this->frequency}++;

            if (!$tag->save()) {
                continue;
            }

            $updatedTags[] = $tag; // populate updated tags 
            $rows[] = [$this->owner->getPrimaryKey(), $tag->getPrimaryKey()];
        }

        if (!empty($rows)) {
            $this->owner->getDb()
                ->createCommand()
                ->batchInsert($pivot, [key($relation->via->link), current($relation->link)], $rows)
                ->execute();
        }
        // update relation for owner model. 
        $this->owner->populateRelation($this->relation, $updatedTags);

Multiple instances in one model

First, let me thank you for having created such an awesome extension.

I' wondering if I can implement multiple instances of Taggable in one model by using a different behavior name for each instance, like this:

public function behaviors()
    {
        return array_merge(parent::behaviors(), [
            'patronages' => [
                'class' => Taggable::className(),
                'relation' => 'Patronages',
                'name' => 'patronage',
                'attribute' => 'patronages'
            ],
            'types' => [
                'class' => Taggable::className(),
                'relation' => 'Types',
                'name' => 'Type',
                'attribute' => 'types'
            ],
        ]);
    }

When tried to do this, it seems to store the results of data entry for the first attribute but ignores the second. I've tried debug other sources of the error, and I can't find any, I wanted to check with you on having multiple instances before I spend additional time debugging..

Extension break when $owner have multiple primary key

Hi,
I'm experiencing some problem in using your extension when i have a table with multiple primary key that have a relationship with tag table. The extension breaks the QueryBuilder because it is trying to insert an associative array into the relationship table rather than inserting an id.

Currently, i;m handling this problem by using this quick fix:

$owner_primary_key = $this->owner->getPrimaryKey();
if(is_array($owner_primary_key)){
        $owner_primary_key = reset($owner_primary_key);
 }
 $rows[] = [$owner_primary_key, $tag->getPrimaryKey()];

If you have a better solution or fix for this problem, i'll be very happy to use it.

How to filter in gridview

I have installed the extension, everything is working fine.
but couldn't get an idea how to make filter/search to work in gridview.
any documentation/guide for that?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.